问题
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.
回答1:
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,
})
})
}
回答2:
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.
来源:https://stackoverflow.com/questions/49216135/react-setstate-and-await