-
-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: avoid shared value reads during render #662
Conversation
📊 Package size report
|
@IvanIhnatsiuk would you mind to have a look? |
const [initialHeight] = useState(() => -reanimated.height.value); | ||
const [initialProgress] = useState(() => reanimated.progress.value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm, they already use useState under the hood https://github.com/software-mansion/react-native-reanimated/blob/main/packages/react-native-reanimated/src/hook/useSharedValue.ts#L19 and if we have a look at the react docs
initialState: The value you want the state to be initially. It can be a value of any type, but there is a special behavior for functions. This argument is ignored after the initial render.
So, are you sure that you need these changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but new REA 3.16 will complain if you use .value
during render. So you should void the usage of .value
during re-render (but you can use for the first render).
I had several approaches:
-
To use
useMemo
to calculate initial values forreanimated.*.value
- but this is not reliable, becauseuseMemo
in future may throw cache away and re-calculate memoization. -
To use
useState
to calculate initial value. -
to create own
useLazySharedValue
hook with next implementation:
export function useSharedValue<Value>(initialValue: () => Value): SharedValue<Value> { // function instead of value
const [mutable] = useState(() => makeMutable(initialValue()));
useEffect(() => {
return () => {
cancelAnimation(mutable);
};
}, [mutable]);
return mutable;
}
If you have more ideas @IvanIhnatsiuk then I'll be happy to consider your thoughts 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm, looks like it would be better to implement your own hook and remove it once people migrate to REA 3.16
Also, it helps to remove code duplication like this:
const [initialHeight] = useState(() => -reanimated.height.value);
const heightWhenOpened = useSharedValue(initialHeight);
and replace it with your own hook
const heightWhenOpened = useLazySharedValue(() => -reanimated.height.value);
But anyway, it's up to you 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, there are few reasons why I don't want to implement my hook:
makeMutable
is publicly exported, but I don't want to copy internal implementation ofuseSharedValue
(even though this hook is a very simple). If it changes over time I'll have to replicate these changes in this package as well.- we have kind of code duplication, but thus we extract values and then reuse the value (I'm not sure if reading
SharedValue
on first render is an expensive operation or not, but I saw somewhere that it adds an overhead - but maybe mistaken).
This is why I think useState
approach is better. anyway, it's pretty minor code modification - in future we may revisit this code 🙂
484a50c
to
42c30b6
Compare
📜 Description
Fixed popping up warnings when re-render happens if
KeyboardAvoidingView
is used.💡 Motivation and Context
Reading
.value
during render is a bad approach because it violates rules of react, so we shouldn't use it anymore.Fortunately we have only one place in the codebase - it's
KeyboardAvoidingView
. Technically I don't violate rules ofInitially I had 3 approaches in my head:
1️⃣ Precompute initial value via
useMemo
The first approach I was thinking of was this:
However later on in react docs I found this:
So I thought that in future potentially
useMemo
may be re-evaluated, so we'll get this warning again. Which is not desirable.2️⃣ Memoize the initial value via
useState
Another approach was the usage of
useState
:In this case we derive value (kind of preparing them) and we are sure that those values will not be accidentally re-calculated (because in such way state will be initialized only one time).
The only downside is that we are creating additional variables and we use state not for its purpose (such values will never be updated actually).
3️⃣ create
useLazySharedValue
hookThe implementation could look like:
However I have next concerns in my head:
makeMutable
is publicly exported, but I don't want to copy internal implementation ofuseSharedValue
(even though this hook is a very simple). If it changes over time I'll have to replicate these changes in this package as well.Keeping all possible ways in my head I decided to go with approach 2. In future I can adjust the logic if needed.
Closes #649
📢 Changelog
JS
useState(() =>
;🤔 How Has This Been Tested?
Tested locally on iPhone 16 Pro iOS 18.0.
📸 Screenshots (if appropriate):
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2024-10-28.at.18.16.57.mp4
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2024-10-28.at.18.17.42.mp4
📝 Checklist