Reputation: 123
Similar question here, but doesn't actually answer the question.
I have run into a lot of problems while using React when it comes to updating state. It's an everyday occurrence that I call setState
and by the time I need to read that state it's still not set. I understand that setState
supports a callback function as its second parameter, but what if I don't want layers and layers of nested callbacks? Why can I not simply await
a setState call to ensure that the state is actually updated by the time I need it?
Also, state
isn't stored on some remote server, it's in-memory. So why would assigning values to variables in memory need to be asynchronous at all?
Upvotes: 4
Views: 2236
Reputation: 807
Hang on, I've got a solution for this.
import { Component } from 'react';
/* eslint-disable */
/**
* Allows you to await setState calls rather than use the callback function
* Declaration: private setStateAsync = setStateAsyncFactory(this);
* Usage: await this.setStateAsync({myProp: val});
* @param component
*/
export const setStateAsyncFactory = <P, S>(
component: React.Component<P, S>
) => {
return <K extends keyof S>(state:
((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) |
Pick<S, K> | S | null) => {
return new Promise<void>(resolve => component.setState(state, resolve));
};
};
/**
* Allows you to await setState calls rather than use the callback function
* Usage: await setStateAsyncDirect(component, {myProp: val});
* Usage: await setStateAsyncDirect(this, {myProp: val});
* @param component
*
*/
export const setStateAsyncDirect = <P, S, K extends keyof S>(
component: React.Component<P, S>,
state:
((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) |
Pick<S, K> | S | null
) => new Promise<void>(resolve => component.setState(state, resolve));
/* eslint-enable */
Usage:
class BlankPage extends React.PureComponent<BlankPageProps, BlankPageState> {
private setStateAsync = setStateAsyncFactory(this);
private myAsyncMethod = async () => {
await this.setStateAsync({myStateProp: true});
}
}
Upvotes: 0
Reputation: 219077
but doesn't support await?
Because it doesn't return a Promise
. Not all asynchronous operations are Promise
-based. "Asynchronous" is a more generic term describing any operation which will happen at some point, maybe right now, maybe whenever, and you should not expect the result immediately.
Ordering a pizza is an asynchronous operation which also doesn't support await
.
Now, before you run with the idea of manually wrapping it in a Promise
, understand that this will probably fail spectacularly. Because the framework doesn't make this Promise
-based on purpose.
State updates are not just asynchronous, they are also batched. So within your operation you can update state multiple times. React isn't going to (and shouldn't) immediately re-render on each of those updates. Instead, your ongoing blocking logic is going to continue and may update state again and again.
When that operation completes, all of those state updates will be processed. The same value may be updated multiple times, multiple values may be updated, multiple calls to update may have been made but with no actual changes to state (setting it to the value it already has), etc.
Once all of that has been processed, if state has changed then the component will re-render with the new state.
by the time I need to read that state it's still not set
Then you're doing it wrong. The updated state is available on the re-render. If you need to respond to the updated state, that's what useEffect
is for. But during your state-updating operation you don't need state to be updated. Because you already have the value(s) to which you are updating it.
but what if I don't want layers and layers of nested callbacks?
Then don't have them. You don't need them. Creating them is likely the result of misunderstanding the framework in the first place.
Upvotes: 1