I am new to react and hooks and I am trying to set the disabled state in the below code on the condition if state.items.length is greater than 3 but I am not getting the updated values in my state object.
So I tried to set the disabled state in the useEffect hook where I get the latest values of the state.
But if I setDisabled state in useEffect it goes into an infinite loop.
Can anyone tell me what is wrong with the code?
//This is how my state object and input fields looks.
const [state, setState] = useState({
items: [],
value: "",
error: null
});
<input
className={"input " + (state.error && " has-error")}
value={state.value}
placeholder="Type or paste email addresses and press `Enter`..."
onKeyDown={handleKeyDown}
onChange={handleChange}
onPaste={handlePaste}
/>
const handleKeyDown = evt => {
if (["Enter", "Tab", ","].includes(evt.key)) {
evt.preventDefault();
var value = state.value.trim();
if (value && isValid(value)) {
setState(prev => ({
...prev,
items: [...prev.items, prev.value],
value: ""
}));
}
//if my items array which is a count of emails i.e arrays of strings is greater than 3 I want to disable the input field.
if(state.items.length > 3){
setDisabled(true);
}
}
};
useEffect(()=>{
// if I set the disabled state which is an object inside the state param it goes into an infinite loop.
passStateToParent(state);
}[state])
You should start by declaring a new variable to hold and keep track of the disabled state. (use another useState)
Then next you should use useEffect to constantly check on the length of items in current state.
I have taken code from above mentioned codesandbox as a refernce.
// use this useState hook to keep track disabled state.
const [inputDisable, setInputDisabled] = useState(false);
//use effect to check, if state item length
useEffect(() => {
const items = [...state.items];
if (items.length === 3) {
setInputDisabled(true);
}
}, [state]);
Followed by this add a new attribute named disable in your input tag and assign the value of inputDisable to it.
Refer to this codesandbox link to see the live example.
https://codesandbox.io/s/vigorous-stallman-vck52?file=/src/App.js:490-523
Related
I have 2 check boxes with state variables "isApproved, setIsApproved" and "isPlayer, setIsPlayer"
After both of these values are assigned, I need to perform some operation say getDetails(isApproved, isPlayer)
The way I know if these 2 state variables are set is by using useEffect()
useEffect(()=>{
getDetails(isApproved, isPlayer)
},[isApproved,isPlayer])
But the issue with this is, whenever user clicks on checkbox, one of these state variable value changes and again "getDetails" gets called
I want to call getDetails only for the first time after these 2 state variables are set
Any suggestions please?
Use a ref to toggle when the action is called, and avoid calling the action if the ref is true or nothing has changed from the initial values:
const initialIsApproved = false
const initialIsPlayer = false
const Demo = () => {
const [isApproved, setIsApproved] = useState(initialIsApproved)
const [isPlayer, setIsPlayer] = useState(initialIsPlayer)
const called = useRef(false)
useEffect(() => {
if(!called.current // would abort if the ref is true
&& isApproved !== initialIsApproved
&& isPlayer !== initialIsPlayer) {
called.current = true // set the ref to true
getDetails(isApproved, isPlayer)
}
}, [isApproved, isPlayer])
return (...)
}
Component For Either Creating Or Editing The User
A part of the code below where I am setting the isActive state conditionally inside useEffect
I am getting the other fields of user_data and successfully updating the state
but only setIsActive is not updating the state
function CreateUser() {
const [isActive, setIsActive] = useState<boolean | undefined>();
useEffect(() => {
if (params_user_id?.id != null) {
const SINGLE_USER_URL = `users/${params_user_id.id}/edit`;
const getSingleUser = async () => {
const {data} = await axios.get(SINGLE_USER_URL)
console.log(data)
setIsActive(data.isactive)
console.log('isactive', isActive)
}
getSingleUser();
}
}, [params_user_id])
return (
<>
<Form.Check
defaultChecked={isActive}
className='mb-3'
type='checkbox'
id='active'
onChange={e => setIsActive(!(isActive))}
label='Active: Unselect for deleting accounts.'/>
</>
)
}
Form.Check From Bootstrap-React
When I hit the Edit page
I did try many things like ternary operator etc
checkBox is not checked bcoz it's undefined
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
In your case you're already using useEffect to update the state, but if you want to act upon that state change, or simply to log it's value, then you can use another separate useEffect for that purpose.
Example:
useEffect(() => {
console.log(isActive)
// Whatever else we want to do after the state has been updated.
}, [isActive])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "isActive" in the example is interchangeable with whatever other state piece you're dealing with.
Check the documentation for more info about this.
Additional comments:
Best to avoid using the loose != to check for equality/inequality and opt for strict inequality check instead, i.e. - !==
Loose inequality is seldom used, if ever, and could be a source for potential bugs.
Perhaps it would better to avoid typing this as boolean | undefined.
Unless justified, this sounds like another potential source for bugs.
I would suggest relying on just boolean instead.
First, you can't get the state's updated value immediately after setting it because State Updates May Be Asynchronous
useEffect(() => {
if (params_user_id?.id != null) {
const SINGLE_USER_URL = `users/${params_user_id.id}/edit`;
const getSingleUser = async () => {
const {data} = await axios.get(SINGLE_USER_URL)
console.log(data)
setIsActive(data.isactive)
console.log('isactive', isActive) // <-- You can't get updated value immediately
}
getSingleUser();
}
}, [params_user_id])
Change the type of useState to boolean only and set the default value to false.
const [isActive, setIsActive] = useState<boolean>(false);
Then, in Form.Check, update onChange to this:
<Form.Check
defaultChecked={isActive}
className='mb-3'
type='checkbox'
id='active'
onChange={() => setIsActive((preVal) => !preVal)}
label='Active: Unselect for deleting accounts.'
/>
i have a react-select component in which i am filing options from api data, whenever i am editing name of a person from api, the names in the options change but the name on the value remains same.
i want to change the value of select after i change the name of a person.
--here i am updating the name of a person using react-query
const deliveryPersonUpdate = (values) => {
const id = deliveryBoy && deliveryBoy.id;
const params = { deliveryBoyId: id, ...values };
updateDeliveryPerson.mutate(params, {
onSuccess: (data) => {
toggle();
setDeliveryBoy(data?.delivery_boy);
queryClient.invalidateQueries([GET_DELIVERY_BOY_LIST.name]);
toast.success("Category Updated");
},
onError: (error) => {
showErrorMessages({ error });
},
});
};
const handleSubmit = (values) => {
deliveryPersonUpdate(values);
};
--select component
<Select
onChange={(obj) => handleChange(obj)}
paintBg
noOptionsMessage={noOptionsMessage}
name="deliveryBoySelect"
className={cx(styles.addDeliveryBoySelect)}
defaultValue={
!isEmpty(data?.delivery_boys) && {
label: data?.delivery_boys?.[0].name,
value: data?.delivery_boys?.[0].id,
}
}
options={data?.delivery_boys.map((d) => ({
label: d.name,
value: d.id,
}))}
isSearchable
/>
--handle change for select
const handleChange = (e) => {
const deliveryBoyData = data.delivery_boys.find(
(obj) => obj.id === e.value
);
setDeliveryBoy(deliveryBoyData);
};
--here, after changing name of person, options are updating right away but the value is not updating
The problem you are facing is that you are using defaultValue for the select, which means the select is uncontrolled. Any time the defaultValue is changed, the select doesn't react to this change because it's not supposed to, it's just the default value.
You have two options:
make the select controlled (by using state)
make the select keyed (by re-mounting it when default value changes)
As for controlled select
You would have to replace defaultValue by value but also attach an onChange handler that changes the state. You kind of already have the onChange as it updates the state in RQ, but since you are using defaultValue, it doesn't propagate back. However, if you just used value I think there would be blinking because RQ is async by nature, so the user could see a frame where the value is still out of sync. So in order to fully do this, you would have to introduce a sync state as well.
const [value, setValue] = useState(props.value)
const onChange = (e) => {
setValue(e.target.value)
props.onChange(e.target.value)
}
useEffect(() => {
setValue(props.value)
}, [props.value])
<select value={value} onChange={onChange} />
What this does it that it keep a local state using setState, handles update from props using useEffect and uses value instead of defaultValue thanks to that. You could also lift the state up if necessary.
As for keyed component
In case the previous solution is not ergonomic or not good for any reason and the component is small-ish, you can also decide that instead of keeping the local state, you will just unmount the component and mount a fresh instance. What the result is, is that when the value changes, it mounts a new component so it can use defaultValue again. You keep the select unchanged except for adding a key prop.
<select key={props.value} defaultValue={props.value} onChange={onChange}>
Having the key the same as the value means that they will be in sync. When the local value changes, it doesn't blink, because we do not set value, the DOM updates on it's own and when the props.value finally changes is async manner by RQ, it create the component anew, making it with the current defaultValue.
const handleFormSubmit = (event) => {
event.preventDefault()
let match = persons.filter(person => person.name === newName)
if (!(match.length > 0 && window.confirm(`${newName} is already added to phonebook, replace the old number with a new one?`))) {
let newPerson = {name: newName, number: newNumber, id: persons[persons.length - 1].id + 1}
ContactServices
.create(newPerson) //this is a function linked to API to update the phonebook
.then(response => {
setPersons(persons.concat(response))
setFilterPersons(persons)
setNewName('')
setNewNumber('')
//the four setState above does not work but setMessage works
setMessage(`new number ${newPerson.name} is added`)
setTimeout(() => {
setMessage(null)
}, 5000)
})
}
//...rest of the code
I'm having problems figuring out why only some of my setStates don't work. setMessage works but not setPersons or setNewName. I tried passing in a function instead a new object into setState, and console.log within the callback function. It worked so it seems that the function is executed but the new state is just not saved? I cannot use useEffect in a callback function here either.
change this condition to something meaningful for javascript this condition always returns false so your code inside ** if ** never works
window.confirm(`${newName} is already added to phonebook, replace the old number with a new one?`)){
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it runs before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
Example:
useEffect(() => {
// Should show updated state -
console.log(state);
}, [state])
The callback will run only after the state has finished changing and a render has occurred.
Note: "state" in the example is interchangeable with whatever state piece you're dealing with in your case, be it persons / newName / newNumber etc..
I have this simple onChange function, but I have problem. When I change my input first time and for example enter "1" my variable inputOne is returns empty string:
const [inputOne, setInputOne] = useState('');
const onChange = (e) => {
setInputOne(e.target.value);
console.log(e.target.value); // first changing of input returns 1
console.log(inputOne); // first changing of input returns EMPTY string
}
<div className="container">
<input type="number" name='input1' value={inputOne} onChange={onChange} />
</div>
but when I change this input again and add one more "1"(in total 11) my console is:
console.log(e.target.value); // returns 11
console.log(inputOne); // returns 1
Why it's happening with my variable inputOne?
New code:
const [inputOne, setInputOne] = useState('');
useEffect(() => {
console.log(inputOne);
}, [inputOne])
const onChange = (e) => {
setInputOne(e.target.value);
console.log(e.target.value);
setTimeout(() => {
if(e.target.value){
const filteredpost = posts[0].filter(mfo => mfo.minPrice <= Number(inputOne));
setPostsToShow(filteredpost.slice(0, 20));
setPost(filteredpost);
}else{
const filteredpost = posts[0];
setPostsToShow(filteredpost.slice(0, 20));
setPost(filteredpost);
}}, 1000);
}
setState is an async function.
In your case, setInputOne queues the change and returns a Promise,
that will not be resolved until the next tick (or even later, if reacts thinks it is worth it to gain some performance).
So the timeline is like this:
Type into input
Trigger onChange
setInputOne (queue the change)
console.log (the value that is queued)
console.log (the variable that is queued)
next tick and consequently the change of the variable.
You can see this with the useEffect hook:
useEffect(() => {
console.log(`tell me when inputOne changes`);
}, [inputOne])
UPDATE
inputOne will never be your updated value inside the onChange function. The onChange function stores the last value until re-render.
Pass your setTimeout to the useEffect OR change inputOne to e.target.value since they will always be the same.
State update in Reactjs is an asynchronous process, therefore it won't be reflected immediately in the next line due to it's asynchronous nature.
If you want to monitor the state whenever its updated, you can use useEffect hook, and place inside its the dependency array the piece of state you want to track.
In your case:
useEffect(() => {
console.log(inputOne);
}, [inputOne])
This will be triggered, every time inputOne changes. If you want to use the value from the inputOne to call another function you should implement that logic inside the useEffect, instead of doing it inside the function onChange which updates the inputOne state.
useEffect(() => {
if(inputOne){
const filteredpost = posts[0].filter(mfo => mfo.minPrice <= Number(inputOne));
setPostsToShow(filteredpost.slice(0, 20));
setPost(filteredpost);
}
else
{
const filteredpost = posts[0];
setPostsToShow(filteredpost.slice(0, 20));
setPost(filteredpost);
}
}, [inputOne]);
Get rid of the timeout. It's unnecessary.