I've created a transition which extends the width property of a search bar to smoothly push buttons next to the search bar away. The goal is to make a unified search bar which gets used across horizontal tabs, where the user can use swipe gestures to navigate between.
I tried to only use transform translations instead of the width, but that would've not been so easily possible because the Search Bar actually resizes and gets wider. I'd have to recalculate borderRadius proportional to the scale if I'm going to animate scaleX etc.
So animating width looks fine on iOS, even on lower end devices (iPhone 6).
Here's a demo:


Of course you can't really see it in a 25 FPS GIF, but on a real device it looks pretty stuttery on Android, even on newer flagship models.
width) in the flexbox system which moves something out of the way (e.g. the buttons to the right)translateX in my caseI expect the width transition to run somewhat smoothly
It runs at very low FPS and has weird stuttering effects which looks like it's jumping around.
Animated Style
const searchBarStyle = useAnimatedStyle(() => {
const index = translateX.value / -SCREEN_WIDTH;
const width = interpolate(
index,
[0, 1, 2],
[
// those are just sample values, assuming that one button has 20px width
180, // 1 button
200, // 0 buttons
160, // 2 buttons
]
);
return { width };
}, []);
JSX View
<Reanimated.View style={[styles.searchBar, searchBarStyle]}>
<HeaderSearchBarContainer style={styles.fill} />
</Reanimated.View>
{visibleButtons === "chats" && (
<>
<InboxButton
style={styles.button}
/>
<NewChatButton
style={styles.button}
/>
</>
)}
{visibleButtons === "camera" && (
<AddEventButton
style={styles.button}
/>
)}
Note: I am actually unmounting the buttons (see
visibleButtonsstate) depending on the Swiper's position. That's done with auseAnimatedReactionand only triggers state updates once per page switching, so that can't be the source of the problem.
Hello!
Congratulations! Your issue passed the validator! Thank you!
@mrousavy I see in notes that you use useAnimatedReaction. Can it be related to infinite evaluation of useAnimatedReaction?
I have a bug that useAnimatedReaction infinitely is called while I was expected it will be called only if shared value is updated.
https://github.com/breeffy/react-native-bottom-sheet in example/src/screens/DynamicExamples.tsx shows that clearly.
If I watch animatedPosition using useDerivedValue I see it is finite:
useDerivedValue(() => {
console.log(
`animatedPosition: ${animatedPosition.value}, animatedPositionIndex: ${animatedPositionIndex.value}`
);
return animatedPosition.value;
}, []);
but useAnimatedReaction is called infinitely:
useAnimatedReaction(
() => (_animatedPosition !== undefined ? animatedPosition.value : null),
(value: number | null) => {
if (value !== null) {
_animatedPosition!.value = value;
}
},
[]
);
_animatedPosition is a shared value passed to component as a property.
@likern I believe that's happening because you're modifying the value you're reading. Essentially your animated reaction re-executes when any of the dependencies change, and the dependencies are _animatedPosition and animatedPosition. Once the reaction is re-evaluated, you assign _animatedPosition, causing it to re-evaluate the reaction again, and so on.
Either way, useAnimatedReaction seems like it's missing a shallow-equality check for the dependencies in the animated reaction.
But in my case this is not the problem, the problem is that animating the width property performs pretty bad. I know it doesn't have the same performance as some transform property (e.g.: translate, scale), but it shouldn't be this laggy.
Hello!
Congratulations! Your issue passed the validator! Thank you!
@likern I see yet another problem with the code you pasted. @mrousavy is right about the infinite loop but also I found that ternary in the first worklet a bit weird:
() => (_animatedPosition !== undefined ? animatedPosition.value : null),
How come _animatedPosition could be undefined here? That would indicate there was a conditional hook call, that shouldn't occur.
@mrousavy As for the issue itself: I wasn't able to reproduce this. I'm pasting my code below. No dropped frames/FPS problems, nothing. Everything works smoothly. It's hard to say what could be the cause of this problem with such limited code you provided.
my code
import Animated, {
useSharedValue,
withTiming,
useAnimatedStyle,
useAnimatedGestureHandler,
interpolate,
} from 'react-native-reanimated';
import { View, Button, StyleSheet } from 'react-native';
import React from 'react';
import { PanGestureHandler } from 'react-native-gesture-handler';
export default function App() {
const width = useSharedValue(1);
const uas = useAnimatedStyle(() => {
const x = interpolate(width.value, [1, 2], [100, 300]);
console.log('here', x);
return {
width: x,
};
});
const handler = useAnimatedGestureHandler({
onEnd: (e, ctx) => {
let target = width.value;
if (e.velocityX > 200) {
target = 2;
} else if (e.velocityX < -200) {
target = 1;
}
if (width.value !== target) width.value = withTiming(target);
},
});
return (
<View>
{/* }
<Button
title="move"
onPress={() => {
width.value = width.value === 100 ? 350 : 100;
}}
/>
{ */}
<View style={{ flex: 1, flexDirection: 'row', width: 200 }}>
<PanGestureHandler onGestureEvent={handler}>
<Animated.View
style={[
styles.item,
{
backgroundColor: 'powderblue',
},
uas,
]}
/>
</PanGestureHandler>
<View style={[styles.item, { backgroundColor: 'skyblue' }]} />
<View style={[styles.item, { backgroundColor: 'steelblue' }]} />
</View>
</View>
);
}
const styles = StyleSheet.create({
item: {
width: 50,
height: 100,
margin: 5,
},
});
@gorhom @karol-bisztyga Thank you.
This is s bug in v3 bottomsheet.
@mrousavy any update on this? Could you please come up with a reproducible example for this one?
@karol-bisztyga sorry, really busy with personal stuff atm. I'll create a repro for this as soon as I can, hopefully this week!
@mrousavy sure, no problem, I understand, thanks!
i think #1610 related to this too
Doesn't seem like it is, but I'll check once it's resolved and there will be a repro.