react-native-reanimated-carousel
react-native-reanimated-carousel copied to clipboard
fix: flicker when starting pan: `panOffset` value race condition
What: the bug
When onGestureUpdate
is called, it may be using the previous value for panOffset
(rather than the value just set in onGestureStart
). This seems to cause a flicker on slow devices since a translation
is shown that is from a different screen.
I see this flicker most obviously on my Android simulator (please see video demonstration below), but the problem exists on other devices as well.
Why
This is due to updates to shared values created via useSharedValue()
updating asynchronously (see https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#remarks ).
This flicker seemed to get worse after this commit https://github.com/dohooo/react-native-reanimated-carousel/commit/b654f439e905bc1d45f5cbb1fd291f3a82848368 - "feat: replaced onScrollBegin with onScrollStart" (I'm not sure if there's a corresponding PR?).
This is due to the order of the handlers being triggered: onBegin
then onStart
then onUpdate
...
-
Before this commit, the
panOffset
was saved duringonBegin
-- which would often happen much longer beforeonUpdate
. -
After this commit, the
panOffset
is saved duringonStart
-- which might happen immediately beforeonUpdate
and thus may not allow enough time for the asynchronous update to occur.
What: proposed fix
Let's add a check to make sure that panOffset
has a valid value before we try to use it.
- When a pan is not active,
panOffset
will have a value ofundefined
. - (Upon pan start, in
onGestureStart
, setpanOffset
to its correct value. This is not a change from existing code.) - In
onGestureUpdate
andonGestureEnd
, check to ensure thatpanOffset
is notundefined
before attempting to use it. - In
onGestureEnd
, resetpanOffset
toundefined
(since pan is no longer active).
Screengrab video recordings
These recordings were taken on an Android simulator.
buggy behavior on 4.0.0-alpha.10
(unpatched)
Notice the flicker to a different "page" when starting pan (i.e. starting touch).
https://github.com/dohooo/react-native-reanimated-carousel/assets/589004/94d33111-2d01-42af-a182-458e83e4d086
improved behavior with patch
https://github.com/dohooo/react-native-reanimated-carousel/assets/589004/f3cd5968-136e-433b-b5ce-152bf4720dc9
🦋 Changeset detected
Latest commit: a99f069b5c4bace6e0cf42d457d3d3363321dd7f
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 1 package
Name | Type |
---|---|
react-native-reanimated-carousel | Patch |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
The latest updates on your projects. Learn more about Vercel for Git ↗︎
Name | Status | Preview | Comments | Updated (UTC) |
---|---|---|---|---|
react-native-reanimated-carousel | ✅ Ready (Inspect) | Visit Preview | 💬 Add feedback | May 6, 2024 4:31pm |
@nmassey have you found a workaround while you wait this to be merged? I have a release blocked because of this (I guess)
@BrodaNoel wrote:
@nmassey have you found a workaround while you wait this to be merged? I have a release blocked because of this (I guess)
I use yarn patch to get this in (along with a couple of other tweaks I have).
Are you on the Discord?
@nmassey I just joined that Discord, but if you can share the patch here, will be awesome!
I had created and applied this patch, but it doesn't fix the issue I mention in https://github.com/dohooo/react-native-reanimated-carousel/issues/590 (which I thiiiink it's the same as you are trying to fix here)
diff --git a/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx b/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx
index 41e39b4..f71128f 100644
--- a/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx
+++ b/node_modules/react-native-reanimated-carousel/src/components/ScrollViewGesture.tsx
@@ -65,7 +65,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
const maxPage = dataLength;
const isHorizontal = useDerivedValue(() => !vertical, [vertical]);
const max = useSharedValue(0);
- const panOffset = useSharedValue(0);
+ const panOffset = useSharedValue<number | undefined>(undefined); // set to undefined when not actively in a pan gesture
const touching = useSharedValue(false);
const validStart = useSharedValue(false);
const scrollEndTranslation = useSharedValue(0);
@@ -290,6 +290,20 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
const onGestureUpdate = useCallback((e: PanGestureHandlerEventPayload) => {
"worklet";
+ if (panOffset.value === undefined) {
+ // This may happen if `onGestureStart` is called as a part of the
+ // JS thread (instead of the UI thread / worklet). If so, when
+ // `onGestureStart` sets panOffset.value, the set will be asynchronous,
+ // and so it may not actually occur before `onGestureUpdate` is called.
+ //
+ // Keeping this value as `undefined` when it is not active protects us
+ // from the situation where we may use the previous value for panOffset
+ // instead; this would cause a visual flicker in the carousel.
+
+ // console.warn("onGestureUpdate: panOffset is undefined");
+ return;
+ }
+
if (validStart.value) {
validStart.value = false;
cancelAnimation(translation);
@@ -334,6 +348,10 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
const onGestureEnd = useCallback((e: GestureStateChangeEvent<PanGestureHandlerEventPayload>, _success: boolean) => {
"worklet";
+ if (panOffset.value === undefined) {
+ return;
+ }
+
const { velocityX, velocityY, translationX, translationY } = e;
scrollEndVelocity.value = isHorizontal.value
? velocityX
@@ -379,6 +397,8 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
if (!loop)
touching.value = false;
+
+ panOffset.value = undefined;
}, [
size,
loop,
I had created and applied this patch, but it doesn't fix the issue I mention in #590 (which I thiiiink it's the same as you are trying to fix here) ...
In addition to patching the source code, you'll also need to patch the relevant built lib/
files in the plugin. (When you use the plugin from your code, those are the actual JS files that get used.)
You can either patch the source code by hand (possible, but annoying), or patch the target plugin in its own repo and rebuild it to generate the relevant lib/
code (and then copy those files over). I sent some instructions for how I do this on Discord.
Note that you can confirm that your patch is running by adding some easily-recognizable code to the relevant lib/
file. e.g. you can add console.log('hello 123')
to a specific lib/
file to make sure that it is getting executed in your local development build.
I hope this helps! 🤓🙏 And, catch you on Discord!
@nmassey can you please share the versions of all the packages you are using? I applied your PRs but still some small issues (which we talked in Discord) and I'm thinking it may be related to other libraries' versions
when it would be released? 😄
Incredible work, thank you! But before merging, could you gen a changeset for this PR?
npx changeset
so it is now ready to merge imho
^up
I also see this problem on Alpha 12, on both emulator and real devices, but only android.
When will this patch be released?
Waiting for this patch sooo much too!
Any update for this issue? Would be nice to start using 4.0 but Alpha 12 still has flickering issues.
@dohooo any chance this can be merged soon?
Subscribing to know when this is merged.
Going back to old RNCarousel temporarily 🥺
hi @Crizzooo - I've been using yarn patch to unblock me while waiting for these PRs to be merged 😕 Are you on the Discord?
hi @Crizzooo - I've been using yarn patch to unblock me while waiting for these PRs to be merged 😕 Are you on the Discord?
One of our engineers just joined and is applying the patch for us :)