If you need to simultaneously
- update some states such that the UI re-renders
- and update some animation variables (useSharedValue, useAnimatedStyle etc.)
what’s the best pattern for this?
I tried the following:
- The same code updates the state and simultaneously assigns new values to the Shared Values.
- Update the state, then in the render method, use an useEffect to change the Shared Values.
- Update the state, then in the render method, use a setTimeout(…, 0) to change the Shared Values.
Regardless of which one I use, I always get a short time where the UI is rendered in an invalid state – for example, the newly rendered UI is there, but the Shared Values are still old. Or, the Shared Values get updated before the UI render is finished. This results in ugly flickers in the UI. These flickers are not deterministic. They happen sometimes, but sometimes not. It seems there is some kind of race condition.
Before I start to analyze this further, what is the “correct” way to do it, from a theoretical standpoint? How can I sync these two updates such that both changes get visible at the same time?
EDIT: Since I still do not get any answers, I spent one more day isolating the problem. Seems I now have sort of a repro:
https://github.com/yolpsoftware/rea-3-bugrepro-838
If is isn’t possible to sync the UI and JS thread in such a situation, we are also welcoming workarounds to solve the problem. Please see the README of the repo for requirements for a solution.
2
Answers
Here are a few approaches to try to sync state updates with React Native animated values updates:
Sequence updates:
Call setState to update state
In the state update callback, update animated values
This sequences them so state update finishes before animated values.
Use LayoutEffect:
Call setState
In the next render, use useLayoutEffect to update animated values
LayoutEffect fires synchronously after render, so values should be in sync.
Batch updates:
Combine state and animated value updates into one batched operation using React Native’s Animated.batch()
This ensures they happen atomically.
Use requestAnimationFrame:
Call setState
In next render, use requestAnimationFrame to schedule animated update
RAF commits changes on next frame so they should be in sync.
Move animated logic to effect:
Call setState
Move animated logic/transition to useEffect
Add dependencies so it only runs after state update
Some other things to try:
Simplify updates to remove unnecessary re-renders
Add shouldComponentUpdate check
Consider useReducer for state updates
The key is to sequence or batch the updates so they commit changes atomically without races.
Here is my approach:
at give time render 3 elements on the screen, 2 of them will be visible on screen and one will be rendered outside the screen, the third screen will be used to animate when the first element is panned, so that we don’t see empty screen.
the entire project can be found here: GitHub Link
values:
create pan gesture handler by using the new gesture detector from gesture handler:
Note: above the index is changed only when animation is finished so that we can change the rendered items, before updating the state to render next set of elements reset all the animation values
file:
here is the example gif how it will work when there is load in js thread: