React setState of boolean value not updating - javascript

New to React, trying to update the state of an object where on property already has a set boolean value. However, it seems like the state is not updating.
I understand that state is update asynchronously, maybe that could coming into play here? I don't believe I can use the setState method that takes an object and callback function because I need access the the previous state.
Here is my initial state:
items: [
{
id: 0,
title: 'Dev Grub',
selected: false
},
...
]
And here is my event handler:
handleCardClick(id, card) {
this.setState((preState, props) => ({
[preState.items[id].selected]: [preState.items[id].selected] ? false : true
}));
console.log('new state: ', this.state.items[id].selected);
}
I've also tried this instead of the ternary: ![card.selected]

updating just a property at the second level of the state won't work. use something like below:
handleCardClick(id, card) {
let items = [...state.items];
items[id].selected = items[id].selected ? false : true
this.setState(() => ({
items
}));
}

React setState doesn't work this way, it doesn't update state right away but rather enqueues the change to update it at some point in the future.
If you want to do something as soon as the state has been updated you can use callback parameter
this.setState((preState, props) => ({
[preState.items[id].selected]: [preState.items[id].selected] ? false : true
}), () => console.log('new state: ', this.state.items[id].selected);)
See docs on setState

setState is async, you console log after the state has been changed like ths
handleCardClick = (id, card) => {
this.setState(
{
[this.state.items[id].selected]: [this.state.items[id].selected]
? false
: true,
},
() => console.log('new state: ', this.state.items[id].selected),
);
};

Related

Update state value of single object in an array

I have list of items where I want to display a loader and hide it upon completing certain action.
For example, here is my array items
[
{
"id": "69f8f183-b057-4db5-8c87-3020168307c5",
"loading": null
},
{
"id": "30d29489-0ba9-4e00-bc28-8ad34ff1a285",
"loading": true
},
{
"id": "5f54ebbd-d380-4a54-bb1d-fc6c76dd1b72",
"loading": false
}
]
I am adding item to array with loading value as null the reason is. I want to process as soon as the the state is updated, hence I am using useEffect hook to observe for any change, if any new item with loading value null is added, then I proceed for action.
My problem is, when I try to modify a single loading value to false, it gives me weird behaviour and set all loading value to false.
What I want to have it, when I change the loading value of a single item in array, then the UI should re-render only for the changed item.
If you want to have a look at fiddle with working example, here is the link https://codesandbox.io/s/d8lh4-d8lh4
Where am I going wrong here?
It's simple use this code:
setTimeout(() => {
setItems((existingItems) =>
existingItems.map((item) =>
item.id === newItem?.id ? { ...item, loading: false } : item
)
);
}, 2000);
Looking at your code, I think the issue is related to accessing the wrong value of newItem and items in setTimeout. Both of them can be solved by doing something similar to the one below.
const handleUpload = newItem => {
// set loading to false to new item after 2 seconds
setTimeout(
theNewItem => {
setItems(exisitingItems =>
exisitingItems.map(item =>
item.id === theNewItem.id ? { ...item, loading: false } : theNewItem,
),
);
},
2000,
newItem,
);
};
You have [items] dependency in your useEffect, which is calling setItems in loop in your handleUpload function.

How to remove unchecked checkbox from React state array?

With a checkbox onChange event, how do I remove value from state array when unchecked in react?
State array:
this.state = { value: [] }
onChange function:
handleChange = event => {
if (event.target.checked) {
this.setState({
value: [...this.state.value, event.target.value]
});
} else {
this.setState({
value: [this.state.value.filter(element => element !== event.target.value)]
});
}
};
Not sure exactly what the .filter() should be doing
You're very close, except:
You need to remove the [] around your call to filter. filter returns an array. If you wrap that in [], you're putting the array inside another array, which you don't want (in this case).
and
Since you're updating state based on existing state, it's important to use the callback version of setState, not the version that directly accepts an object. State updates can be batched together, so you need to be sure you're dealing with the most recent version of the array.
So:
handleChange = ({target: {checked, value: checkValue}}) => {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ^− destructuring to take the properties from the event,
// since the event object will get reused and we're doing
// something asynchronous below
if (checked) {
this.setState(({value}) => ({value: [...value, checkValue]}));
} else {
this.setState(({value}) => ({value: value.filter(e => e !== checkValue)}));
// ^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^−−− No [] around this
}
};
There are some situations where you'd get away with using this.state.value instead of using the callback (for instance, if you only update value in response to certain events), but you have to be sure you know which ones they are; it's simpler just to use the callback.
FWIW, since it has multiple values in it, if it were me I'd call the state property values (plural) rather than value, which would also mean we didn't have to rename the value from the event target in the destructuring above:
handleChange = ({target: {checked, value}}) => {
if (checked) {
this.setState(({values}) => ({values: [...values, value]}));
} else {
this.setState(({values}) => ({values: values.filter(e => e !== value)}));
}
};

useState won't update state

I'm trying to update a state using useState hook, however the state won't update.
const handleSelect = address => {
geocodeByAddress(address)
.then(address => {
const receivedAddress = address[0];
const newAddress = {
street: receivedAddress.address_components[1].long_name,
number: receivedAddress.address_components[0].long_name,
city: receivedAddress.address_components[3].long_name,
country: receivedAddress.address_components[5].long_name,
zip: receivedAddress.address_components[6].long_name,
selected: true
};
handleAddressSelection(newAddress);
})
.catch(error => console.log(error));
};
When handleSelect is called, it creates the object newAddress, and then calls handleAddressSelection passing newAddress.
function handleAddressSelection(newObj) {
console.log(newObj);
Object.keys(newObj).forEach(function(key) {
setValues({ ...values, [key]: newObj[key] });
});
}
In console.log(newObj) the object is filled fine, with all the data I need. Then I call setValues for each object in newObj, however no matter what, the values object won't receive the new data. The only one that is updated is selected: true, all others won't update.
What should I do to fix it?
You're calling setValues multiple times in a loop, and every time you do so, you spread the original values, and thus overwrite anything that was done on the previous setValues. Only the very last setValues ends up working, which happens to be the one for selected: true
If you need to base your update on the previous value of the state, you should use the function version of setValues, as in:
Object.keys(newObj).forEach(function(key) {
setValues(oldValues => ({ ...oldValues, [key]: newObj[key] }));
});
But even better would be to only call setValues once. If you're calling it multiple times, then you're going to generate multiple renders. I'd do this:
setValues(oldValues => ({...oldValues, ...newObj}));
Values is not even defined anywhere in your examples. My guess is, it's some cached copy and you should be using callback variant of the state setter instead:
setValues(previousValues => ({ ...previousValues, [key]: newObj[key] }));

Updating parent component only after multiple child components have completed running

I have a Parent react component with multiple child components that are created through a .map() function. I am passing in a function addCallback() as child props so I have a reference and can trigger all child's handleRun() function via the Parent.
I'm trying to update state of my Parent component to running = true when all children are running and to running = false and render said status on the parent when all children have completed running. However the state doesn't seem to update in the particular sequence I specify.
Here is how I'm doing it:
let promise1 = this.setState({isRunning: true},
() => {
this.state.childRef.map(x => x())
});
Promise.all([promise1])
.then(() => this.setState({isRunning: false}))
Here's the entire code in codesandbox: link
Would appreciate your help as I'm still pretty new to React (and Javascript in general). Thanks!
Cause runSomething is not a Promise. You must change.
runSomething() {
return new Promise((resolve, reject) => {
this.setState({ status: "running" });
// simulate running something that takes 8s
setTimeout(() => {
this.setState({ status: "idle" });
resolve(true);
}, 3000);
});
}
A working sandbox here https://codesandbox.io/s/fragrant-cloud-5o2um
Using async in a function declaration automatically returns a Promise wrapped around whatever you are returning from your function. In your case, it's undefined. This is why your current code is not throwing any errors at the moment.
You will need a mechanism to wait for the setTimeout. Changing the runSomething function like this will work
async runSomething() {
this.setState({ status: "running" });
// simulate running something that takes 8s
return new Promise(resolve => {
setTimeout(() => {
this.setState({ status: "idle" }, resolve);
}, 3000);
});
}
Do notice the line this.setState({ status: "idle" }, resolve);. It makes sure that your promise resolves not only after the setTimeout but also after the child's state is changed to "idle". Which is the correct indication that your child component has moved to "idle" state.
Codesandbox: https://codesandbox.io/s/epic-boyd-12hkj
Here is the sandbox implementation of what you are trying to achieve. Sanbox
Here i have created a state in parent component that will be updated when child is running.
this.state = {
callbacks: [],
components: [
{
index: 0, // we don't need this field its just for your info you can just create [true,false] array and index will represent component index.
status: false
},
{
index: 1,
status: false
}
]
};
When all the status in component array is true we update the idle status of parent to running.
getAllRunningStatus() {
let { components } = this.state;
let checkAllRunning = components.map(element => element.status);
if (checkAllRunning.indexOf(false) === -1) { // you can also use !includes(false)
return true;
}
return false;
}
inside your render function
<h1>Parent {this.getAllRunningStatus() ? "running" : "idle"}</h1>
Note:- I have just written a rough code. You can optimise it as per your requirements. Thanks

How to initialize from setstate in Reactjs?

_handleClickFilter(value, xname, chartId){
console.log("dataSourceId", this.state.dataSource);
this.setState({
filterData: [{
filter: "equals",
value:value ,
attribute:xname,
}]
});
let filterdefinitions = {
dataSourceId : "59ef50d6e4b054efd6d8aa53",
filterDefinitions: this.state.filterData,
}
let data = {
filterDefinitions: [filterdefinitions],
};
DashboardAction._ApplicableFilterToDashboard(data, this.props.params.dashboardId);
DashboardAction._ApplicableFilterToChart(data, this.props.params.dashboardId, chartId);
DashboardAction._saveFilterToDashboard(data, this.props.params.dashboardId);
}
I am able to get values in the setstate which I want. But the values are not getting set. Showing the values exists in the this.state only.
thanks in advance
setState is async, so it's not guaranteed that the state will be set after you have used it until the re-render is triggered. You should be very careful about using the state right after you have set it, a more 'reactive' approach is usually better. However, if you want to make sure that you'll be able to access the new state, you can use the second argument of setState which is a callback function that will be called when the state is set.
You can use it this way:
_handleClickFilter(value, xname, chartId){
this.setState({
filterData: [{
filter: "equals",
value:value ,
attribute:xname,
}]
}, () => {
let filterdefinitions = {
dataSourceId : "59ef50d6e4b054efd6d8aa53",
filterDefinitions: this.state.filterData,
}
let data = {
filterDefinitions: [filterdefinitions],
};
DashboardAction._ApplicableFilterToDashboard(data, this.props.params.dashboardId);
DashboardAction._ApplicableFilterToChart(data, this.props.params.dashboardId, chartId);
DashboardAction._saveFilterToDashboard(data, this.props.params.dashboardId);
});
}
Replace your set state code with this
this.setState({ 
filterData: this.state.filterData.map((data, index) => {
data.filter = "equals",
data.value = value,
data.attribute=xname
}) });

Categories

Resources