Is there anyway to sync setState in react - reactjs

As we've known, setState is async. I've read few questions about setState, on how to use the value right after setState, but those aren't what I need right now.
I'm trying to set value for array List, and then use that List to do a function to get the value for Result. If setState isn't async, then it would be like this
`
handleChange(e) {
const resultList = this.state.list.slice();
resultList[e.target.id] = e.target.value;
this.setState({
list: resultList,
result: this.doSomething(resultList) // this.doSomething(this.state.list)
});
}
`
Is there anyway to achieve this? A documentation or keyword to research would be awesome.
Many thanks

There is a callback parameter to setState which is called after the state has been updated
this.setState({
list: resultList,
result: this.doSomething(resultList)
}, () => {
//do something with the updated this.state
});

You can use async await like
async handleChange(e) {
const resultList = this.state.list.slice();
resultList[e.target.id] = e.target.value;
this.setState({
list: resultList,
result: await this.doSomething(resultList) // this.doSomething(this.state.list)
});
}

The use of this.state together with this.setState is discouraged, exactly because state updates are asynchronous and may result in race conditions.
In case updated state derives from previous state, state updater function should be used. Because it's asynchronous, event object should be treated beforehand, due to how synthetic events work in React:
const { id, value } = e.target;
this.setState(state => {
const list = [...state.list];
list[id] = value;
return {
list,
result: this.doSomething(list)
};
});

Related

setState is not updating in my other method

I'm very new to react and i'm confused why my state is not updated in another method of mine see example below.
fetchMovies = () => {
const self = this;
axios.get("https://api.themoviedb.org/3/trending/movie/day?api_key=XXXXXXX")
.then(function(response){
console.log(response.data)
self.setState({
collection: response.data.results
})
console.log(self.state.collection)
});
}
makeRow = () => {
console.log(this.state.collection.length);
if(this.state.collection.length !== 0) {
var movieRows = [];
this.state.collection.forEach(function (i) {
movieRows.push(<p>{i.id}</p>);
});
this.setState({
movieRow: movieRows
})
}
}
componentDidMount() {
this.fetchMovies();
this.makeRow();
}
When inside of fetchMovies function i can access collection and it has all the data but this is the part i can't understand in the makeRow function when i console log the state i would of expected the updated state to show here but it doesn't i'm even executing the functions in sequence.
Thanks in advance.
the collection is set after the async call is resolved. Even though makeRow method is called after fetchMoview, coz of async call, u will never know when the call will be resolved and collection state will be set.
There is no need to keep movieRows in the state as that is just needed for rendering. Keeping html mockup in the state is never a good idea.
So u should just call fetchMoviews in the componentDidMount and render the data in as follows:
render() {
const { collection } = this.state;
return (
<>
{
collection.map(c => <p>{c.id}</p>)
}
</>
)
}
make sure the initial value for collection in the state is [] .
The setState() documentation contains the following paragraph:
Think of setState() as a request rather than an immediate command
to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
To access the modified state you need to use the function signature setState(updater, [callback]), so in your case it should be;
self.setState({
collection: response.data.results
}, () => { // Will be executed after state update
console.log(self.state.collection)
// Call your make row function here and remove it from componentDidMount if that is all it does.
self.makeRow()
} )

Updating state - why creating a new copy of state when calling setState?

React docs:
Never mutate this.state directly, as calling setState() afterwards
may replace the mutation you made. Treat this.state as if it were
immutable.
That's clear.
class App extends React.Component {
state = {
data: []
}
the following I understand
updateState(event) {
const {name, value} = event.target;
let user = this.state.user; // this is a reference, not a copy...
user[name] = value; //
return this.setState({user}); // so this could replace the previous mutation
}
this following I don't understand
updateState(event) {
const {name, value} = event.target;
let user = {...this.state.user, [name]: value};
this.setState({user});
}
I understand (as in previous example), that I should not either only:
mutate state directly without calling setState; or
mutate it and then use setState afterwards.
However, why can't I just (without direct mutation) call setState without creating a new copy of state (no spread operator/Object.assign)? What would be wrong with the following:
getData = () => {
axios.get("example.com") ...
this.setState({
data:response.data
})
}
Why should it be:
getData = () => {
axios.get("example.com") ...
this.setState({
data:[...data, response.data]
})
}
render (){
...
}
}
What would be wrong with the following:
this.setState({
data: response.data,
});
Absolutely nothing, unless you don't want to replace the contents of this.state.data with response.data.
Why should it be:
this.setState({
data: [...data, response.data],
});
Because with spread you are not loosing the contents of this.state.data - you are basically pushing new response into the data array.
Note: You should use callback inside setState to get access to current data from this.state.
this.setState((prevState) => ({
data: [...prevState.data, response.data],
}));

Redux Not Updating in Component Synchonously

I was under the impression that redux was synchronous but it does not seem to be in this case. I have this:
loadData() {
const filterData = {};
Object.values(this.props.filters).map(f => {
filterData[f.filter] = f.selectedValue || '';
});
this.props.fetchData({filters: filterData}); //redux call to load data
}
filterOnSubmit(filter, value, display) {
const newFilter = { filter, selectedValue: value, display };
this.props.updateFilter(newFilter);
this.loadData();
}
But the value of f does not include the selected value that is set BUT when I look at my redux devtools it is there. If I delay the mapping (or delay the loadData via putting it in a setState callback) it is there also.
action and reducer for reference:
case UPDATE_FILTER: {
const newFilter = { ...state[action.payload.filter], ...action.payload };
return { ...state, [action.payload.filter]: newFilter };
}
export function updateFilter(newFilterData) {
return {
type: UPDATE_FILTER,
payload: newFilterData
};
}
Although the redux store update is synchronous, the changes are not reflected in the same React cycle, the updates go though a re-render state, calling update lifecycle method.
After updating the store, you can call the actions that need to be called on store update in lifecycle method like componentDidUpdate (or componentWillReceiveProps which is now soon to be deprecated) or otherwise pass the filters to the loadData function directly
loadData(filters) {
const filterData = {};
Object.values(filters).map(f => {
filterData[f.filter] = f.selectedValue || '';
});
this.props.fetchData({filters: filterData}); //redux call to load data
}
filterOnSubmit(filter, value, display) {
const newFilter = { filter, selectedValue: value, display };
this.props.updateFilter(newFilter);
this.loadData(newFilter);
}
Using componentDidUpdate, you would write it like
componentDidUpdate(prevProps) {
// you have to perform an isEqual check since simply using '===' will not work for comparing arrays
if(!_.isEqual(prevProps.filters, this.props.filters)) {
this.loadData();
}
}
loadData() {
const filterData = {};
Object.values(this.props.filters).map(f => {
filterData[f.filter] = f.selectedValue || '';
});
this.props.fetchData({filters: filterData}); //redux call to load data
}
filterOnSubmit(filter, value, display) {
const newFilter = { filter, selectedValue: value, display };
this.props.updateFilter(newFilter);
}
You're on the right way, use componentDidUpdate to start a new fetch request. A thing you're possibly missing is an if-condition for changes of filter prop.
Docs remind about this:
You may call setState() immediately in componentDidUpdate() but note
that it must be wrapped in a condition like in the example above, or
you’ll cause an infinite loop.
And for your case it might be something like this, depending on the shape of filters:
componentDidUpdate(prevProps) {
if (this.props.filter !== prevProps.filter)
this.loadData();
}
Depending on code outside, you also might consider using a deep comparison inside if. But, of course, the best way would be to stay with === comparison and optimize mapStateToProps from outside (e.g. use meomization).
Do as SAmab said or move this
Object.values(this.props.filters).map(f => {
console.log(f);
filterData[f.filter] = f.selectedValue || '';
});
to your render function.

React setState and await

While the functional setState() is recommended when the current state is being used, the function still cannot be async. How could we develop a function that uses the state, calls a REST API and changes the state right after?
This is the original function, that disregards the async behavior of setState.
onSortColumnChanged = (sortColumn) => async (event) => {
const prev = this.state;
​ const searchParams = { ...prev.params, sortColumn: sortColumn };
​ const result = await this.callSearch(searchParams);
​ this.setState({ params: searchParams, result: result });
}
If we change the above function to use functional setState, VSCode complains that await can only be used in async functions.
onSortColumnChanged = (sortColumn) => (event) => {
this.setState((prev) => {
​ const searchParams = { ...prev.params, sortColumn: sortColumn };
​ const result = await this.callSearch(searchParams);
​ return { params: searchParams, result: result };
});
}
I think I'm missing something fundamental here.
Short answer
onSortColumnChanged = (sortColumn) => async (event) => {
const searchParams = { ...this.state.params, sortColumn: sortColumn };
const result = await this.callSearch(searchParams);
this.setState(prev => {
prev.params.sortColumn = sortColumn
prev.result = result
return prev
})
}
Long answer
Why use setState callbacks
The React docs state that multiple calls to setState may be batched together.
Let's pretend you have a chat app. You might have a function that looks like this:
async incoming_message(message){
this.setState({messages: this.state.messages.concat(message)})
}
So if 2 messages come in a few milliseconds apart, React may batch these two calls to setState()
this.setState({messages: [].concat(message_1)}) // 1
this.setState({messages: [].concat(message_2)}) // 2
and when 2 runs, we will lose message_1.
Providing setState with a transform function is the solution
async incoming_message(message){
this.setState(prev => {
prev.messages.push(message)
return prev
})
}
In the callback-based setState, the 1st message will not be lost, since instead of specifying an exact state to write, we specify a way to transform the old state into the new state.
Important notes
The object version of setState() does a shallow merge of the new state and the previous state. This means that calling setState() with an object will only update the keys that you pass in.
If you choose to use the callback version of setState() instead, you are expected to return the entire new state. So when you have
return { params: searchParams, result: result };
in your setState() callback, you will lose all state value except params and result.
Your code
the functional setState() is recommended when the current state is being used
It is important to understand why the functional setState() is recommended. If your code will not break when multiple setState() calls are batched together, it is OK to pass an object instead of a function.
If you still want to use the callback pattern, you don't need to do all the work in your setState() callback, you only need to specify how to transform the old state into the new one.
onSortColumnChanged = (sortColumn) => async (event) => {
const {params} = this.state;
const searchParams = { ...params, sortColumn: sortColumn };
const result = await this.callSearch(searchParams);
this.setState(prev => {
// prev is the old state
// we changed sortColumn param
prev.params.sortColumn = sortColumn
// and we changed the result
prev.result = result
// and we return the new, valid state
return prev
// or if you prefer an immutable approach
return Object.assign({}, prev, {
params: {...prev.params, sortColumn: sortColumn},
result: result,
})
})
}
Think of 'await' as a callback function of promise. So you don't have promise here and you are calling callback function. Hence, VSCode ask you to add the promise which in this case it is 'async' function.

this.setState does not update state

I'm trying to use this.setState within handleFormSubmit however this.setState isn't updating and I'm not sure why. If I run console.log(updatePosition) before this.setState I can that all the data is there. What am I missing? I use similar code for handleChange and I don't have problems.
constructor(props) {
super(props);
let uniqueId = moment().valueOf();
this.state = {
careerHistoryPositions: [{company: '', uniqueId: uniqueId, errors: {} }],
};
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
handleFormSubmit(event) {
event.preventDefault();
const { careerHistoryPositions } = this.state;
const updatePosition = this.state.careerHistoryPositions.map((careerHistoryPosition) => {
const errors = careerHistoryValidation(careerHistoryPosition);
return { ...careerHistoryPosition, errors: errors };
});
console.log(updatePosition)
this.setState({ careerHistoryPositions: updatePosition });
}
Keep in mind that the state isn't updated immediately. If you want to check if it's updated use callback function. Something as follows:
this.setState({ careerHistoryPositions: updatePosition }, () => console.log(this.state.careerHistoryPositions);
From the docs :
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Hope this helps.
You should show how you are calling handleFormSubmit chances are that it's bound to a Dom event. So this is not the class/component, instead if you console.log(this); you'll see that it's the Dom element, the form element.
To make your code work as intended, in your component constructor() method, add this to rebind the handler function to the react component's class method, and you'll have access to this.state and this.setState()
this.handleFormSubmit = this.handleFormSubmit.bind(this);

Resources