I've created a "twitter style" button that when pressed opens up a sub-menu of items that can be selected/"tweeted" about.
The button is simple in that when pressed, it triggers a function with Animated events:
const toggleOpen = () => {
if (this._open) {
Animated.timing(animState.animation, {
toValue: 0,
duration: 300,
}).start();
} else {
Animated.timing(animState.animation, {
toValue: 1,
duration: 300,
}).start(); // putting '() => setFirstInteraction(true)' here causes RenderItems to disappear after the animation duration, until next onPress event.
}
this._open = !this._open;
};
and here's the button that calls this function:
<TouchableWithoutFeedback
onPress={() => {
toggleOpen();
// setFirstInteraction(true); // this works here, but the button doesn't toggleOpen until the 3rd attempt.
}}>
<Animated.View style={[
styles.button,
styles.buttonActiveBg,
]}>
<Image
style={styles.icon}
source={require('./assets/snack-icon.png')}
/>
</Animated.View>
</TouchableWithoutFeedback>
I need to add a second useState
function that is called at the same time as toggleOpen();
. You can see my notes above regarding the problems I'm facing when using the setFirstInteraction(true)
useState function I'm referring to.
Logically this should work, but for some reason when I add the setFirstInteraction(true)
it seems to block the toggleOpen()
function. If you persist and press the button a few times, eventually the toggleOpen()
will work exactly as expected. My question is, why does this blocking type of action happen?
You can reproduce the issue in my snack: https://snack.expo.dev/@dazzerr/topicactionbutton-demo . Please use a device. The web preview presents no issues, but on both iOS and Android the issue is present. Line 191 is where you'll see the setFirstInteraction(true)
instance.
CodePudding user response:
Your animatedValue
isn't stable. This causes it to be recreated on each state change. It is advised to useRef
instead (though, useMemo
would do the trick here as well).
const animState = useRef(new Animated.Value(0)).current;
Your toggleOpen
function can also be simplified. In fact, you only need a single state to handle what you want and react on it in a useEffect
to trigger the animations that you have implemented.
I have called this state isOpen
and I have removed all other states. The toggleOpen
function just toggles this state.
const [isOpen, setIsOpen] = useState(false)
const toggleOpen = () => {
setIsOpen(prev => !prev)
}
In the useEffect
we react on state changes and trigger the correct animations.
const animState = useRef(new Animated.Value(0)).current;
useEffect(() => {
Axios.get('https://www.getfretwise.com/wp-json/buddyboss/v1/forums')
.then(({ data }) => setData(data))
.catch((error) => console.error(error));
}, []);
useEffect(() => {
Animated.timing(animState, {
toValue: isOpen ? 1 : 0,
duration: 300,
useNativeDriver: true,
}).start();
}, [isOpen, animState])
I have adapted your snack. Here is a working version.
Remarks: Of course, you still need for your data to be fetched from your API. The opacity change of the button is still the same and it remains disabled until the data has been fetched.