How can I use lottie animation in GestureHandler React-native - react-native

Today I want to use lottie animation in GestureHandler from react-native reanimated as I said above. But something went wrong. When i call lottie.current.play(0, 15); for exemple code -
const gestureHandler = useAnimatedGestureHandler({
onStart: (_) => {
lottie.current.play(0, 15);
},
onActive: () => {
},
onEnd: (_) => {
},
});
I got error
ERROR TypeError: null is not an object (evaluating
'lottie.current.play')
I can't understand why?
I have made a reference to the object
const lottie = useRef();
.
.
.
<Lottie
source={require("./assets/cart.json")}
style={{ width: 100, height: 100, alignSelf: "center" }}
autoPlay={false}
loop={false}
ref={lottie}
resizeMode="contain"
/>
Also found that I can use lottie.current.play(0, 15); the function in React.useEffect but why i cant use in in this events onStart, onActive, onEnd

Basically onStart, onActive and onEnd are full-fledged worklets, i.e.
javascript functions that will be executed on the UI Thread.
Consequently if you have functions that can only be executed on the
javascript thread and need to be launched from a worklet it must
always be specified with runOnJS.
So i should -
const playAnim = (num) => {
lottie.current.play(num);
};
runOnJS(playAnim)(15);

Related

useState function seems to block Animated.timing event?

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.
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.

react-native-reanimated useSharedValue does not update in jest tests

I'm currently trying to figure out how to test reanimated 2 animations using useSharedValue.
What makes 0 sense to me is the example given from reanimated.
https://github.com/software-mansion/react-native-reanimated/blob/master/tests/SharedValue.test.js
If the button is supposed to increment its sharedValue by 1 every time you press it. Why would you write a test that shows it does NOT change???
I've tried it myself and yea, the value does not update itself.
I want to assert that the value has changed in my test:
ParallaxScrollView.tsx
const scrollY = useSharedValue(0);
const onScroll = useAnimatedScrollHandler((event) => {
scrollY.value = event.contentOffset.y;
});
return (
<Animated.Image
style={{height: scrollY}}
testID="header-image"
source={{ uri: headerImage }}
resizeMode="cover"
/>
)
ParallaxScrollView.test.tsx
const { getByTestId } = render(<ParallaxScrollView {...defaultProps} />);
const headerImage = getByTestId('header-image');
const content = getByTestId('parallax-content');
const eventData = {
nativeEvent: {
contentOffset: {
y: 100,
},
},
};
fireEvent.scroll(content, eventData);
expect(headerImage).toHaveAnimatedStyle({ height: 100 }); //Received is 0
useAnimatedScrollHandler uses react-native-gesture-handler to handle events but at this moment gesture-handler doesn't support events in tests yet, this is what I am working on. Look at this - https://github.com/software-mansion/react-native-gesture-handler/pull/1762
I think this will be available soon. If you want to be up to date, please open an issue in the Reanimated Github.

Animation and state issue: using Animated.spring inside useEffect

I'm new to React-Native app development.
https://snack.expo.io/RjmfLhFYg
I'm trying to understand why the animation stops working if I un-comment line 11 setTest(false) in the linked expo snack.
Thank you!
Relevant code copy:
export default function App() {
const element1 = useRef(new Animated.ValueXY()).current;
const element2 = new Animated.ValueXY();
const [test, setTest] = useState(true);
useEffect(() => {
// setTest(false);
setTimeout(() => {
Animated.spring(
element2,
{
toValue: {x: -10, y: -100},
useNativeDriver: true
}
).start();
}, 2000);
}, []);
return (
<View style={styles.container}>
<Animated.View
style={[styles.element, {backgroundColor: "blue"}, {
transform: [
{translateX: element2.x}, {translateY: element2.y}
]
}]}>
</Animated.View>
<Animated.View
style={[styles.element, {backgroundColor: "red"}, {
transform: [{translateX: element1.x}, {translateY: element1.y}]
}]}
>
</Animated.View>
</View>
);
}
useState docs
The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component.
Remember that when you change the state of a component it causes a "re-render" so your element2 variable gets back to its initial value. To solve it use the "useRef" hook on the element2 variable just like you did with element1.
const element2 = useRef(new Animated.ValueXY()).current;
"useRef" hook will make the variable persistent through the component life cycle so it won't be affected if the component gets re-rendered
useRef docs
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

setTimeout in Hooks is not changing state

I am trying to upload image and after 20 seconds, if image is not uplaoded I want to close UploadingIndicator component (which appeared with the start of uploading) and display custom WaitAlert component, where user can choose to wait or try to send image again.
But I have problem with displaying custom WaitAlert component. It is not displayed.
Here is my code:
import WaitAlert from '../components/alert/waitAlert'
import UploadingIndicator from '../components/uploadingIndicator/uploadingInddicator'
let sendingTimeout
const App = (props) => {
const [showUploadingIndicator, setShowUploadingIndicator] = React.useState(false)
const [showWaitAlert, setShowWaitAlert] = React.useState(false);
saveAsset = () => {
setShowUploadingIndicator(true)
sendingTimeout = setTimeout(() => {
setShowUploadingIndicator(false)
setShowWaitAlert(true) //THIS IS NOT DISPLAYING WaitAlert
}, 20000)
updateMutation({
variables: {
assets: [{
files
}]
}
}).then(result => {
setShowUploadingIndicator(false)
clearTimeout(sendingTimeout)
setShowWaitAlert(false)
})
.catch((error) => {
console.log("error.toString()", JSON.stringify(error))
})
}
return (
<View>
<TouchableOpacity onPress={saveAsset} />
{showUploadingIndicator &&
<UploadingIndicator />
}
{showWaitAlert &&
<WaitAlert />
}
</View>
);
}
How can I make WaitAlert appeared?
The sendingTimeout is been cleared in clearTimeout(sendingTimeout), then the setTimeout is not triggered after 20 seconds. Try to remove the line clearTimeout(sendingTimeout) to see if it works.
I found the solution.
The problem was in WaitAlert component.
I added style of main View, where modal was placed to:
mainView: {
top: 0,
left: 0,
flex: 1,
position: 'absolute',
zIndex: 10,
},

React Native - changing screen content while animating

I have an animated screen using gesture handler, and reanimated libraries. My problem is I am trying to change the screen content while the animations fired. I have made it work, but honestly I am 100% sure this is not the best solution (or it might be, but need some more configurations)
Code:
const onStateChangeLogin = event([
{
nativeEvent: ({state}) => block([
cond(eq(state, State.END), set(buttonOpacity, runTiming(new Clock(), 1, 0))), call([], () => handleTapLogin())
])
}
]);
In the call function, I am trying to change the content:
const handleTapLogin = (e) => {
setContent(<Login />)}
This is the Animated.View:
<TapGestureHandler onHandlerStateChange={onStateChangeLogin}>
<Animated.View style={{...styles.button, opacity: buttonOpacity, transform:[{translateY: buttonY}]}}>
<Text style={{...styles.buttonText, color: colors.white}}>{strings.signin}</Text>
</Animated.View>
</TapGestureHandler>
And the screen content is located in the bottom of my 'return' method:
....</Animated.View>
</TapGestureHandler>
{content}
</Animated.View>
</View>
</Container>
So it's working now, but too slow. I think because the call function in the event is async. So what happens now, the content is changing, but sometimes I need to wait like 2-3 secs to see the right screen.
I have already tried to use 'listener' in the event function:
const onStateChangeLogin = event([
{
nativeEvent: ({state}) => block([
cond(eq(state, State.END), set(buttonOpacity, runTiming(new Clock(), 1, 0))), call([], () => handleTapLogin())
], {
listener: () => handleTapLogin
}
)
}
]);
And I'm also tried to use onGestureEvent and onHandlerStateChange together like:
<TapGestureHandler onGestureEvent={onStateChangeLogin} onHandlerStateChange={handleTapLogin}>
...
</TapGestureHandler>