This is a minimal repo case for what we are calling the pink-eye bug. Over in the Edge app, our show / hide password icon (which looks like an eye) would randomly turn from mint green to pink.
This is a standard react-native init project, but with App.tsx modified to show the bug. Yes, this has been reported upstream.
The bug exists in react-native-reanimated. Returning an interpolateColor value from a useDerivedValue will cause incorrect colors to appear on iOS. Here is the setup:
const blue = '#2080c0';
const derivedColor = useDerivedValue(() =>
interpolateColor(0, [0, 1], [blue, blue]),
);This should always render as blue if we put it in an animated style:
const animatedStyle = useAnimatedStyle(() => ({
color: derivedColor.value,
}));However, on iOS specifically, the text will glitch to pink after the first button press:
// Cause a re-render on press:
const [count, setCount] = React.useState(0);
const handleCount = () => setCount(count + 1);
return (
<>
<Animated.Text style={animatedStyle}>
{count}
</Animated.Text>
<Button onPress={handleCount} title="Count" />
</>
);Here is what seems to be happening:
- First render
- JS thread:
useDerivedValuereturns the string"rgba(32, 128, 192, 1)" - JS thread:
useAnimatedStylereturns the style{ color: "rgba(32, 128, 192, 1)" } - The text renders as blue
- UI thread:
useDerivedValueruns again, and returns the integer 0xff2080c0. This seems to be some sort of optimization, since this differs from what the JS thread returns.
- JS thread:
- User presses the button
- Second render
- JS thread:
useDerivedValuedoes not run, but returns the saved 0xff2080c0 from the UI thread. - JS thread:
useAnimatedStylereturns the style{ color: 0xff2080c0 } - The text renders as pink
- JS thread:
Directly rendering <Text style={{ color: 0xff2080c0 }}>Hello</Text> produces the same pink color (although TypeScript complains). Messing around with the values, it seems that the JS thread interprets integers as 0xrrggbbaa, whereas the Reanimated UI thread interprets integers as 0xaarrggbb.
As a hacky work-around until the Reanimated team fixes the bug, we are using patch-package to edit node_modules/react-native-reanimated/src/reanimated2/Colors.ts, around line 520:
- if (IS_WEB || !_WORKLET) {
+ if (!IS_ANDROID || !_WORKLET) {
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}This removes a performance optimization, but it does solve the bug.