-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add mutable value read/write during render warning (#6310)
## Summary This PR adds warnings shown when the SharedValue `.value` property is accessed (read or written) while the component is being rendered. The initial render is ignored as all reanimated hooks are executed for the first time during the initial render (e.g. `useAnimatedStyle` builds the initial component style). ## Test plan - open the **Invalid read/write during render** example in the example app, - press on the **Re-render** button to trigger re-render, which will access `.value` property of the shared value from the component function body, - see warnings about invalid read and invalid write --------- Co-authored-by: Tomek Zawadzki <tomasz.zawadzki@swmansion.com>
- Loading branch information
Showing
4 changed files
with
145 additions
and
1 deletion.
There are no files selected for viewing
89 changes: 89 additions & 0 deletions
89
apps/common-app/src/examples/InvalidValueAccessExample.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { Text, StyleSheet, View, Button } from 'react-native'; | ||
|
||
import React, { useEffect } from 'react'; | ||
import Animated, { | ||
configureReanimatedLogger, | ||
useAnimatedStyle, | ||
useSharedValue, | ||
withTiming, | ||
} from 'react-native-reanimated'; | ||
|
||
configureReanimatedLogger({ | ||
// change to `false` or remove the `configureReanimatedLogger` call to | ||
// disable the warning | ||
strict: true, | ||
}); | ||
|
||
export default function InvalidValueAccessExample() { | ||
const [counter, setCounter] = React.useState(0); | ||
const [updateFromUseEffect, setUpdateFromUseEffect] = React.useState(false); | ||
const sv = useSharedValue(0); | ||
|
||
if (!updateFromUseEffect) { | ||
// logs writing to `value`... warning | ||
sv.value = counter; | ||
// logs reading from `value`... warning | ||
console.log('shared value:', sv.value); | ||
} | ||
|
||
useEffect(() => { | ||
if (updateFromUseEffect) { | ||
// no warning is logged | ||
sv.value = counter; | ||
// no warning is logged | ||
console.log('useEffect shared value:', sv.value); | ||
} | ||
}, [sv, counter, updateFromUseEffect]); | ||
|
||
const reRender = () => { | ||
setCounter((prev) => prev + 1); | ||
}; | ||
|
||
const animatedBarStyle = useAnimatedStyle(() => ({ | ||
transform: [{ scaleX: withTiming(Math.sin((sv.value * Math.PI) / 10)) }], | ||
})); | ||
|
||
return ( | ||
<View style={styles.container}> | ||
<View style={styles.row}> | ||
<Text style={styles.text}> | ||
Update from:{' '} | ||
<Text style={styles.highlight}> | ||
{updateFromUseEffect ? 'useEffect' : 'component'} | ||
</Text> | ||
</Text> | ||
<Button | ||
title="change" | ||
onPress={() => setUpdateFromUseEffect((prev) => !prev)} | ||
/> | ||
</View> | ||
<Button title="Re-render" onPress={reRender} /> | ||
<Text>Counter: {counter}</Text> | ||
<Animated.View style={[styles.bar, animatedBarStyle]} /> | ||
</View> | ||
); | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
flex: 1, | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
}, | ||
bar: { | ||
height: 20, | ||
backgroundColor: 'blue', | ||
width: '100%', | ||
}, | ||
text: { | ||
fontSize: 16, | ||
}, | ||
highlight: { | ||
fontWeight: 'bold', | ||
}, | ||
row: { | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
gap: 10, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
'use strict'; | ||
import React from 'react'; | ||
|
||
function getCurrentReactOwner() { | ||
const ReactSharedInternals = | ||
// @ts-expect-error React secret internals aren't typed | ||
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED || | ||
// @ts-expect-error React secret internals aren't typed | ||
React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; | ||
return ReactSharedInternals?.ReactCurrentOwner?.current; | ||
} | ||
|
||
export function isReactRendering() { | ||
return !!getCurrentReactOwner(); | ||
} | ||
|
||
export function isFirstReactRender() { | ||
const currentOwner = getCurrentReactOwner(); | ||
// alternate is not null only after the first render and stores all the | ||
// data from the previous component render | ||
return currentOwner && !currentOwner?.alternate; | ||
} |