React: useState array not updating - javascript

When you push an array, data is pushed. However if you check in console.log, data is not imported. It seems to be a delay. Can you tell me why is this happening?
is there solution for this?
Expected console.log result showing input, however empty array shows and if you click checkbox again then input appears.
const [checked, setChecked] = useState<number[]>([])
const handleAddListToArray = (id: number) => {
console.log(checked)
if (setChecked.includes(id)) {
setChecked(checked.filter((item) => item !== id))
} else {
setChecked([...checked, id])
}
}
--- checkbox compornent ---
const [isChecked, setIsChecked] = useState(false)
const handleChange = () => {
setIsChecked(!isChecked)
handleAddListToArray(id)
}
<Checkbox checked={isChecked} onClick={() => handleChange()} />

when you push an array, data is pushed however if you check in
console.log data is not inported. it seems to be a delay can you tell
me why is this happening?
The state-setting functions are asynchronous. In other words, if you wrote:
const [foo, setFoo] = useState(0);
setFoo(1);
console.log(foo); // logs 0, NOT 1
you would see 0 logged, not 1 ... at least initially. However, there'd be a log entry below that would show 1.
This is because set* function don't change the value in the function, but they do cause the component to be re-rendered after, which means the function is run again, and now uses the correct value..
however empty array shows and if you click checkbox again then input
appears.
It's because of this code:
setIsChecked(!isChecked)
Initially you set isChecked to an array:
setChecked(checked.filter((item) => item !== id))
But then you changed it to !isChecked ... a boolean. Once you change it to a boolean, you can't change it back to an array.

You check the setState-function if it includes the input, on your fourth row of code:
if (setChecked.includes(id))
I believe you want to chech the checked-state instead, like so:
if (checked.includes(id))
Also, consider using the useState-callback when you mutate your state based on the previous one. So instead of:
setChecked(checked.filter((item) => item !== id))
try:
setChecked((prevState) => prevState.filter((item) => item !== id))
You can also use this when you setting your isChecked state. This ensure that you get your latest known state and avoids getting in some wierd states because React updates the state asynchronous.

Some suggestions
if (setChecked.includes(id)) {
should be (setChecked is a function)
if (checked.includes(id)) {
For setChecked(checked.filter((item) => item !== id))
better usage will be
setChecked(prevCheckedValues => prevCheckedValues.filter((item) => item !== id));
This way you will always get the current checked values when you do a setChecked.
Another way to use this is via a useCallback and pass the dependency array as [checked]
If you want to print checked values each time its changed you can use a useEffect (docs) with correct dependency array.
Usage example
useEffect(()=>{
console.log("Checked", checked);
}, [checked])

Related

react useState doesn't update state

I have a table with checkboxes and I want to save selected checkboxes' ids in state. So here's my code.
Input looks like this (I use coreui so this is the inside of a table's scopedSlots):
selected: (item) => {
return (
<td style={{ width: '40px' }}>
<CInputCheckbox
className="mx-auto"
id={item.id}
onChange={(e) => handleSelect(e)}
/>
</td>
);}
And this is the rest:
const [selectedRows, setSelectedRows] = useState([]);
const handleSelect = (e) => {
const id = e.target.id;
const index = selectedRows.indexOf(id);
const rows = [...selectedRows];
if (index === -1) {
rows.push(id);
} else {
rows.split(index, 1);
}
setSelectedRows(rows);
};
And the weirdest thing happens - in the chrome's react devtools I see that the first id is being added to the selectedRows array and then when I select another row - the previous item in the array is being overwritten. When i console.log my selectedRows array it shows empty array always (even if I see in the devtools that there's one item). I have no idea what I'm doing wrong here.
There is not a lot of information to work with in your question, so I had to make many assumptions about how are they suppose to work, but this is what I got so far:
the handleSelect function has a little error when it comes to removing items from the list, your function is using split but I think what you meant was slice
I think the scopes of handleSelect and const [selectedRows, setSelectedRows] = useState([]); are wrong. For what I can see, they are at the same level of selected component, which will fail as they will only be able to keep track of one selected component at the time, and I think you are needing to keep track of multiple selected components.
Here is a working version of what I think you are trying to do
https://codepen.io/richard-unal/pen/eYWXXOo?editors=1111
If you need clarification on something, please let me know, I'm aware that I'm not the best explainer.

UseState how to set an Array back to empty?

I'm trying set clickFavIconArray back to an empty array with the hook.
Basically, the setClickFavIconArray has a list of IDs the showFavIcon() checks that ID and if it contains the same ID I want to remove it from the array and update the setClickFavIconArray to the new Array.
However, it just seems to be adding on to the original clickFavIconArray no matter what. Is there a way to clear the clickFavIconArray state back to an [] empty array?
Some help here would be awesome.
const [clickFavIconArray, setClickFavIconArray] = useState([]);
function showFavIcon(id){
if (clickFavIconArray.includes(id)) {
const newArray = clickFavIconArray.filter(item => !id.includes(item))
setClickFavIconArray(newArray)
}
setClickFavIconArray([...clickFavIconArray, id])
}
Simply pass the new value of empty array to setClickFavIconArray():
setClickFavIconArray([])
To make sure that the id is not immediately added to the array again, add a return statement inside the if-statement.
const [clickFavIconArray, setClickFavIconArray] = useState([]);
function showFavIcon(id){
if (clickFavIconArray.includes(id)) {
const newArray = clickFavIconArray.filter(item => !id.includes(item));
setClickFavIconArray(newArray);
return; // make sure that the next line is not executed
}
setClickFavIconArray([...clickFavIconArray, id])
}
There are two issues with the code
filter function seems to be invalid it should be replaced with
clickFavIconArray.filter(item => id != item)
You are adding id again to the array with this
setClickFavIconArray([...clickFavIconArray, id])
If you want to remove id, there is no need for this line in your code.
However you can always set clickFavIconArray to an empty array state using this code:
setClickFavIconArray([])

how to stop rerendering in react on checkbox check?

I have simple list which is dynamically added on add button click. in my list there is a checkbox is also present .so I have an issue when I toggle the checkbox my whole list is re render why ?
let take example I added A,B,C,D in my list when I toggle D checkbox it should only render D item currently it render whole list why ?
here is my code
https://codesandbox.io/s/stupefied-wildflower-gv9be
const Item = ({ text, checked, onCheckedHandler }) => {
console.log(checked, "ssss");
return (
<div className={checked ? "bg" : ""}>
<span>{text}</span>
<input type="checkbox" onChange={e => onCheckedHandler(e, text)} />
</div>
);
};
Every time items changes (whether by adding a new item or checking a value), you are creating a new onCheckedHandler in your App. This propagates down to your Item component. Since the previous onCheckedHandler property is not referentially equivalent to the previous one, it renders (and you see that console log for each item). Memoizing the component alone won't help because a property being passed to it is changing every time.
To get around that, you need to memoize the onCheckedHandler, try this:
const onCheckedHandler = useCallback((e, selectedText) => {
const target = e.target
setItems(items => {
const i = items.findIndex(i => i.text === selectedText);
let obj = items[i];
obj.checked = target.checked;
return [...items.slice(0, i), obj, ...items.slice(i + 1)];
})
}, [setItems])
The you can wrap your Item compoennt with React.memo, and it should work as expected. You'll also need to import the useCallback the same way you import useState

How I can manage the state for 4 Checkbox in functional component in ReactJS?

How I would create a functional component with exactly 4 checkboxes? I have created dynamically mapping an array and I set its value with:
const [checked, setChecked] = useState([false, false, false, false])
and then, with a function I change its state, something like:
const checkboxes = array.map( (el, index) => {
return <Checkbox
checked={checked[index]}
onChange={checkedHandler}/>
}
This is the handler:
const checkedHandler = (event, index) => {
setChecked(...)
//Well, here I don't know how change the particular value of the array...
}
Or I must create a useState for each Checkbox checked state? And how pass the state of this Checkbox to the father component if it was necessary?
You are invoking the checkedHandler wrong way. You have:
onChange={checkedHandler}
And you should have:
onChange={() => {checkedHandler(index)}}
In the handler itself you should copy your current array to prevent its mutation and change the value of the indexed one:
const checkedHandler = (index) => {
const newChecked = [...checked];
newChecked[index] = !checked[index]; // toogle the checkbox
setChecked(newChecked);
}
Assign id to each checkbox like id="1", id="2" and so on...
Then your checkedHandler() function be like:
checkedHandler(event) {
const newChecked = [...checked]
newChecked[parseint(event.target.id) - 1] = true
setChecked(newChecked)
}
There is ALOT of ways you can do this, if you case is simple, you could just shallow copy the array and change only the index you want.
let newChecked = [...checked]
newChecked[index] = value
setChecked(newChecked)
But for more complex cases, please take a look at Correct modification of state arrays in ReactJS

slice happens in the code but it doesnt update in the UI

there are three drop down menus in the initial state.
after I select the first drop down the second drop down values gets loaded
after I select the second drop down values.
a new set of drop down loads.
when I select remove button of the second set.
it doesnt remove that set but it removes the first set.
when I debugged removeSelectedValue method there slices are happening correctly but its not updating in the updating
can you tell me how to pass the queryComponents values so that it will update in the UI.
can you tell me how to fix it.
so that in future I will fix it myself.
providing my relevant code snippet and sandbox below.
all my code is in demo.js
https://codesandbox.io/s/4x9lw9qrmx
removeSelectedValue = index => {
console.log("removeSelectedValue--->", index);
let seletedValues = this.state.queryComponents;
seletedValues.splice(index, 1);
console.log("spliced Values--->", seletedValues);
this.setState({ queryComponents: seletedValues });
};
render() {
let queryComp = this.state.queryComponents.map((value, index) => {
return (
<AutoCompleteComponent
key={index}
value={value}
index={index}
valueSelected={this.getSelectedValue}
removeSeleted={this.removeSelectedValue}
/>
);
});
return <div>{queryComp}</div>;
}
When you do let seletedValues = this.state.queryComponents;
you're creating a reference to that variable, instead of making a copy.
You need to make sure you replace your state with a new object/array for the re-render to happen.
Please try this:
removeSelectedValue = index => {
this.setState(prevState => ({
queryComponents: prevState.seletedValues.filter((a, i) => (i !== index));
});
};
That filter function is equivalent to the splice you were using, but returns a new array instead of modifying the original one.
On the other hand, I 'm passing setState a function that uses prevState making the code shorter.

Categories

Resources