Skip to content

Commit 566bf52

Browse files
hennessyevandohooo
andauthored
Fixes #861 reanimated value in render (#866)
* fix: Reading from .value of reanimated during render closes #861 * fix: Closes #861 using reanimated value during render * fix: schedule selected value with scheduleOnRN * fix: avoid reanimated value in pagination render * chore: add changeset for pagination render fix --------- Co-authored-by: Caspian 東澔 <caspian.zhao@outlook.com>
1 parent c595958 commit 566bf52

File tree

4 files changed

+51
-10
lines changed

4 files changed

+51
-10
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-native-reanimated-carousel": patch
3+
---
4+
5+
Fix pagination accessibility state by syncing selection with scheduleOnRN instead of reading reanimated values during render, and add coverage to avoid test warnings.

src/components/Pagination/Basic/PaginationItem.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import type { PropsWithChildren } from "react";
2-
import React from "react";
2+
import React, { useState } from "react";
33
import type { ViewStyle } from "react-native";
44
import { Pressable, View } from "react-native";
55
import type { SharedValue } from "react-native-reanimated";
6-
import Animated, { Extrapolation, interpolate, useAnimatedStyle } from "react-native-reanimated";
6+
import Animated, {
7+
Extrapolation,
8+
interpolate,
9+
useAnimatedReaction,
10+
useAnimatedStyle,
11+
} from "react-native-reanimated";
12+
import { scheduleOnRN } from "react-native-worklets";
713

814
export type DotStyle = Omit<ViewStyle, "width" | "height"> & {
915
width?: number;
@@ -55,6 +61,16 @@ export const PaginationItem: React.FC<
5561
const width = sizes.width;
5662
const height = sizes.height;
5763

64+
const [isSelected, setIsSelected] = useState(false);
65+
66+
useAnimatedReaction(
67+
() => animValue.value,
68+
(value) => {
69+
scheduleOnRN(setIsSelected, value === index);
70+
},
71+
[animValue, index]
72+
);
73+
5874
const animStyle = useAnimatedStyle(() => {
5975
const size = horizontal ? height : width;
6076
let inputRange = [index - 1, index, index + 1];
@@ -79,8 +95,8 @@ export const PaginationItem: React.FC<
7995
onPress={onPress}
8096
accessibilityLabel={accessibilityLabel}
8197
accessibilityRole="button"
82-
accessibilityHint={animValue.value === index ? "" : `Go to ${accessibilityLabel}`}
83-
accessibilityState={{ selected: animValue.value === index }}
98+
accessibilityHint={isSelected ? "" : `Go to ${accessibilityLabel}`}
99+
accessibilityState={{ selected: isSelected }}
84100
>
85101
<View
86102
style={[

src/components/Pagination/Custom/PaginationItem.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { PropsWithChildren } from "react";
2-
import React from "react";
2+
import React, { useState } from "react";
33
import { Pressable } from "react-native";
44
import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
55
import type { SharedValue } from "react-native-reanimated";
@@ -10,6 +10,7 @@ import Animated, {
1010
useAnimatedStyle,
1111
useSharedValue,
1212
useDerivedValue,
13+
useAnimatedReaction,
1314
} from "react-native-reanimated";
1415
import { scheduleOnRN } from "react-native-worklets";
1516

@@ -59,10 +60,20 @@ export const PaginationItem: React.FC<
5960
customReanimatedStyleRef.value = customReanimatedStyle?.(progress, index, count) ?? {};
6061
};
6162

63+
const [isSelected, setIsSelected] = useState(false);
64+
6265
useDerivedValue(() => {
6366
scheduleOnRN(handleCustomAnimation, animValue?.value);
6467
});
6568

69+
useAnimatedReaction(
70+
() => animValue.value,
71+
(value) => {
72+
scheduleOnRN(setIsSelected, value === index);
73+
},
74+
[animValue, index]
75+
);
76+
6677
const animStyle = useAnimatedStyle((): AnimatedDefaultStyle => {
6778
const {
6879
width = size || defaultDotSize,
@@ -112,8 +123,8 @@ export const PaginationItem: React.FC<
112123
onPress={onPress}
113124
accessibilityLabel={accessibilityLabel}
114125
accessibilityRole="button"
115-
accessibilityHint={animValue.value === index ? "" : `Go to ${accessibilityLabel}`}
116-
accessibilityState={{ selected: animValue.value === index }}
126+
accessibilityHint={isSelected ? "" : `Go to ${accessibilityLabel}`}
127+
accessibilityState={{ selected: isSelected }}
117128
>
118129
<Animated.View
119130
style={[

src/components/Pagination/Pagination.test.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { fireEvent, render } from "@testing-library/react-native";
22
import { View } from "react-native";
33
import { useSharedValue } from "react-native-reanimated";
4+
import { act } from "react-test-renderer";
45

56
import { Pagination } from ".";
67

8+
jest.mock("react-native-worklets", () => ({
9+
scheduleOnRN: (fn: (...args: unknown[]) => void, ...args: unknown[]) => fn(...args),
10+
}));
11+
712
describe("Pagination.Basic", () => {
813
it("throws when size/dot width/height are non-number", () => {
914
const Test = () => {
@@ -21,7 +26,7 @@ describe("Pagination.Basic", () => {
2126
expect(() => render(<Test />)).toThrow("size/width/height must be a number");
2227
});
2328

24-
it("renders dots and calls onPress", () => {
29+
it("renders dots and calls onPress", async () => {
2530
const handlePress = jest.fn();
2631
const Test = () => {
2732
const progress = useSharedValue(0);
@@ -36,6 +41,7 @@ describe("Pagination.Basic", () => {
3641
};
3742

3843
const { UNSAFE_root } = render(<Test />);
44+
await act(async () => {});
3945

4046
const buttons = UNSAFE_root.findAll(
4147
(node) =>
@@ -50,7 +56,9 @@ describe("Pagination.Basic", () => {
5056

5157
expect(uniqueByLabel).toHaveLength(3);
5258

53-
fireEvent.press(uniqueByLabel[1]);
59+
await act(async () => {
60+
fireEvent.press(uniqueByLabel[1]);
61+
});
5462
expect(handlePress).toHaveBeenCalledWith(1);
5563
});
5664
});
@@ -72,7 +80,7 @@ describe("Pagination.Custom", () => {
7280
expect(() => render(<Test />)).toThrow("size/width/height must be a number");
7381
});
7482

75-
it("uses max item dimensions for container min sizes", () => {
83+
it("uses max item dimensions for container min sizes", async () => {
7684
const Test = () => {
7785
const progress = useSharedValue(0);
7886
return (
@@ -87,6 +95,7 @@ describe("Pagination.Custom", () => {
8795
};
8896

8997
const { UNSAFE_root } = render(<Test />);
98+
await act(async () => {});
9099

91100
// @ts-expect-error
92101
const container = UNSAFE_root.findByType(View);

0 commit comments

Comments
 (0)