I am creating a game based on an interval. A parent component creates an interval and lets its children register callbacks that are fired each interval.
My setup looks like
<ParentComponent>
<Child1 />
<Child2 />
...
</ParentComponent>
The parent uses React.cloneElement
and provides a method to each child
registerCallback( callback, id ) {
const { callbacks } = this.state;
this.setState({
callbacks: callbacks.concat({
callback,
id
})
})
}
This approach worked fine. But now I have two children calling the registerCallback()
method from within componentDidMount()
.
Now only <Child2 />
stores its callback in the parents state.
When I delayed the calling of the registerCallback()
method like
setTimeout(() => {
registerCallback(callbackFunction, id)
}, 50 )
it worked fine. But that feels like a very wrong hack, especially since I need to "guess" how long the state needs to update.
So my conclusion was that the state update is just too slow and instead defined an array outside of my component and updated this instead. The strange thing is that if I mutate this array it works fine.
So if I use
array.push({callback, id})
my array looks like [callback1, callback2]
But since I don't want to mutate my array I tried
array.concat({callback, id})
This leaves me with []
While my state looks like [array2]
How can I improve my method within the parent to give children the possibility to call registerCallback()
whenever where ever without any problem.
As you've correctly deduced, the problem is setState is asynchronous, so by the time second child calls registerCallback, the state change by first child has not yet propagated to this.state. Try:
registerCallback( callback, id ) {
this.setState(({ callbacks }) => ({
callbacks: callbacks.concat({
callback,
id
})
})
}
You pass a function as first parameter to setState. This function receives the current state (above destructured as { callbacks }
) and should return the new state. The function will be invoked either immediately or when a previous state change has propagated, so its state is guaranteed fresh. Thus, you avoid using the potentially stale this.state
.
Here's an article about it:
https://medium.com/@wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3