I am trying to create an InputAccessoryView for my app. It has a TextInput and a Pressable button.
What I would like to do is, when the TextInput has empty value then hide the button, else show the button using react native animation.
I have come up with the working component that works as expected. And for the animations I am utilizing useNativeDriver: true
.
However, there's an issue:
Here is the code and also link to the expo snack:
export default function App() {
const [text, setText] = React.useState('');
const translateY = React.useRef(new Animated.Value(0)).current;
React.useEffect(() => {
if (text.length > 0) {
// animate the add button to show by coming up
_animateShowAddButton();
} else {
// animate the add button to hide by going down
_animateHideAddButton();
}
// return null
}, [text]);
const _animateShowAddButton = () => {
Animated.timing(translateY, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
};
const _animateHideAddButton = () => {
Animated.timing(translateY, {
toValue: 100,
duration: 300,
useNativeDriver: true,
}).start();
};
const onPressFunction = () => {
console.log('onPressFunction');
};
return (
<View style={styles.container}>
<Text style={styles.paragraph}>Some text view</Text>
<View style={styles.myInputAccessory}>
<TextInput
placeholder="Enter text"
style={styles.textInputStyle}
onChangeText={(text) => setText(text)}
/>
<Animated.View
style={[
{
transform: [
{
translateY: translateY,
},
],
},
]}>
<Pressable style={styles.buttonStyle} onPress={onPressFunction}>
<Ionicons name="send" size={24} color="white" />
</Pressable>
</Animated.View>
</View>
</View>
);
}
It seems like the animation is paused (and animates again) whenever the text is changed. Could you help me how to animate the button to show smoothly when the text is not empty?
The problem is that useEffect
is firing on each text change due to the second parameter [text]
. So, the animation is starting on every text change.
You can prevent this by "locking" the animation with a bit of state. Here are the relevant changes to your component:
function App() {
const [isAnimating, setIsAnimating] = useState(false);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
// only start a new animation if we're not currently animating
if (!isAnimating) {
// only animate show if currently invisible
if (!isVisible && text.length > 0) {
_animateShowAddButton();
// only animate hide if currently visible
} else if (isVisible && text.length === 0) {
_animateHideAddButton();
}
}
// check if we need to animate whenever text changes
// also check whenever animation finishes
}, [text, isAnimating]);
const _animateShowAddButton = () => {
// secure the lock so another animation can't start
setIsAnimating(true);
setIsVisible(true);
Animated.timing(translateY, {
toValue: 0,
duration: 300,
useNativeDriver: true,
// release the lock on completion
}).start(() => setIsAnimating(false));
};
const _animateHideAddButton = () => {
// secure the lock so another animation can't start
setIsAnimating(true);
setIsVisible(false);
Animated.timing(translateY, {
toValue: 100,
duration: 300,
useNativeDriver: true,
// release the lock on completion
}).start(() => setIsAnimating(false));
};
}