I'm trying to stop the animation once the data has loaded. For the sake of testing, I'm using a timer to simulate state change (data has loaded) to interrupt animation. The problem is that the animation keeps running after the state has changed.
const [isLoading, setIsLoading] = useState(false);
const animationRef = useRef(new Animated.Value(0)).current;
const loadData = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 3000)
}
useEffect(() => {
const rotateElement = () => {
animationRef.setValue(0);
Animated.timing(animationRef, {
toValue: 1,
duration: 1500,
easing: Easing.linear,
useNativeDriver: true,
}).start(rotateElement);
};
if (isLoading) {
rotateElement();
} else {
Animated.timing(animationRef).stop();
}
}, [isLoading, animationRef]);
const spin = animationRef.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
});
Snack:
https://snack.expo.dev/#wastelandtime/timer
Only try to remove the parameter rotateElement from start.
const rotateElement = () => {
animationRef.setValue(0);
Animated.timing(animationRef, {
toValue: 1,
duration: 1500,
easing: Easing.linear,
useNativeDriver: true,
}).start();
You could set a really long duration, ideally as long as your configured timeout. Then, you wouldn't need to call the start function passing the rotateElement as callback as Ivan suggested. That way, when you call the stop method, it will work as desired.
Another option would be using the react-native-reanimated package for writing this function in a more declarative way, making sure that the animation would run as long as you need it to.
I edited your snack, implementing the reanimated alternative: https://snack.expo.dev/#scalfs/timer
On seeing the code sample, simply using Animated.loop (with the required iterations) should get this working well enough. So the useEffect in the codeblock would be like so,
useEffect(() => {
const rotateElement = () => {
// animationRef.setValue(0); This isn't necessary anymore.
Animated.loop(
Animated.timing(animationRef, {
toValue: 1,
duration: 1500,
easing: Easing.linear,
useNativeDriver: true,
}),
{
iterations: 2,
}
).start(rotateElement);
};
if (isLoading) {
rotateElement();
} else {
Animated.timing(animationRef).stop();
}
}, [isLoading, animationRef]);
As for your use case, the iterations could be set to a high enough number and just use the if statement to stop the loop when loading is complete as you have already done here. You could also add a check for errors as well, so the loop doesn't continue ad infinitum.
You are creating a new instant of rotateElement each time your state changes. The animation you attempt to stop isn't the one you think.
const loadData = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 1000)
}
const rotateElement = Animated.timing(animationRef, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true,
});
useEffect(() => {
if (isLoading) {
rotateElement.start();
} else {
rotateElement.stop();
}
}, [isLoading]);
Best creating the rotateElement outside the useEffect as shown above.
I've also changed the timing of the animation because the demo was too short to simulate end of loading correctly.
Related
I have this function:
const moveSwitch = () => {
setLeftStatus(!leftStatus);
Animated.timing(xValue, {
toValue: leftStatus ? 0 : 26,
duration: 300,
easing: Easing.linear,
useNativeDriver: false,
}).start();
};
It works if I remove the conditional option and setState.
Any suggestions please?
If anyone comes around this problem here is how I solved it.
Since setState function re-renders component animation was not working.
So I just created ref using useRef and updated it like this:
const moveSwitch = () => {
Animated.timing(xValue, {
toValue: leftStatus.current ? 0 : 26,
duration: 300,
easing: Easing.linear,
useNativeDriver: false,
}).start(event => {
if (event.finished) {
if (leftStatus.current) {
return (leftStatus.current = false);
} else {
return (leftStatus.current = true);
}
}
});
};
i am trying to implement a feature when user enters the wrong password the 4 small circles shake to the right and left.
I created an animation component to test my code and it was working but now my problem is how do i apply it only when the password is incorrect?
Currently when i enter an incorrect password nothing happens. What i expect is for the view to move horizontally.
const shake = new Animated.Value(0.5);
const [subtle,setSubtle]=useState(true);
const [auto,setAuto]=useState(false);
const translateXAnim = shake.interpolate({
inputRange: [0, 1],
outputRange: [subtle ? -8 : -16, subtle ? 8 : 16],
});
const getAnimationStyles = () => ({
transform: [
{
translateX: translateXAnim,
},
],
});
const runAnimation = () => {
Animated.sequence([
Animated.timing(shake, {
delay: 300,
toValue: 1,
duration: subtle ? 300 : 200,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
Animated.timing(shake, {
toValue: 0,
duration: subtle ? 200 : 100,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
Animated.timing(shake, {
toValue: 1,
duration: subtle ? 200 : 100,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
Animated.timing(shake, {
toValue: 0,
duration: subtle ? 200 : 100,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
Animated.timing(shake, {
toValue: 0.5,
duration: subtle ? 300 : 200,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
]).start(() => {
if (auto) runAnimation();
});
};
const stopAnimation = () => {
shake.stopAnimation();
};
const handleConfirm = async()=>{
const result = await authApi();
if(!result.ok) {
setAuto(true)
setSubtle(true)
runAnimation()
stopAnimation()
return setLoginFailed(true);
}
setLoginFailed(false);
};
return(
<Animated.View style={[getAnimationStyles()]}>
<View style={styles.circleBlock}>
{
password.map(p=>{
let style =p != ''?styles.circleFill
: styles.circle
return <View style={style}></View>
})
}
</View>
</Animated.View>
issue is that you didn't use useRef() for
const shake = new Animated.Value(0.5);
should be
const shake = useRef(new Animated.Value(0.5)).current;
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.
https://reactjs.org/docs/hooks-reference.html#useref
made separate expo snack with same input and output range values for animation shake effect. check it out.
Expo: https://snack.expo.io/#klakshman318/runshakeanimation
let animatedHeight = new Animated.Value(50);
const animate = () => {
animatedHeight.setValue(50);
Animated.timing(animatedHeight, {
toValue: 0,
duration: 200,
useNativeDriver: true
}).start();
};
const handleSubmit = async (values:ILoginProps) => {
setLoading(true);
axios.post('http://127.0.0.1:3333/api/v1/auth/login', values)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
animate();
setLoading(false);
})
}
<Animated.View style={[style.errorContainer, {transform: [{translateY: animatedHeight}]}]}>
<Text style={style.errorText}>Credentials not found!</Text>
</Animated.View>
I have this code, first I set a loading state and then execute a animate function which runs the Animated function to create the animation effect.
When i have setLoading(true) before animate(), the animation doesn't happen.
I really have no idea why this happens and no idea how to solve this
Use useRef hook to wrap your animated value. React will keep tracking of its value after re-rendering, in other case you might lose it.
let animatedHeight = React.useRef(new Animated.Value(50)).current;
I am trying to achieve loop animation in react native. I am able to stop the animation as well, but it stops only after finishing the running animation in progress. Is there a way to stop it immediately. Since I am changing state value , I don't think I can use Animated.loop.
Edit: I tried stopping animation using
Animated.timing(this.state.opacity).stop()
But this had no effect on animation, it just kept going. So I added a boolean to control it, which works, but only after finishing the ongoing loop of animation.
Here is my code
this.state = {
varyingText: 'location',
stopAnimation: false,
opacity: new Animated.Value(0),
};
componentDidMount = () => {
this.startAnimation();
};
startAnimation = () => {
if (this.state.stopAnimation) return;
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1000
}).start(() => {
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 1000
}).start(() => {
this.setState({
varyingText: 'location 1',
}, () => {
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1000
}).start(() => {
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 1000
}).start(() => {
this.setState({
varyingText: 'location 2',
}, () => {
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1000
}).start(() => {
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 1000
}).start(() => {
this.setState({
varyingText: 'location 3',
}, () => this.startAnimation())
})
})
})
})
})
})
})
});
};
stopAnimation = () => {
this.setState({
stopAnimation: true,
});
};
render() {
const opacityStyle = {
opacity: this.state.opacity,
};
return (
<View style={styles.view}>
<Animated.Text style={...opacityStyle}>
{this.state.varyingText}
</Animated.Text>
<Button
onPress={this.stopAnimation}
title="Stop"
color="#841584"
/>
</View>
);
}
In the code, if I stop the animation when text is location 1, it stops but only after showing location 2.
I have a button in my screen that has a movement animation that keeps going right and left to encourage the user to touch that. Also, I want to have a count down timer, so when time decrease button animation speed up.
I did this :
constructor(props) {
super(props);
this.state = {
timer : 1000,
animateBtnOnTime : new Animated.Value(1),
};
}
const ButtonAnimation =
Animated.loop(
Animated.timing(
this.state.animateBtnOnTime,
{
toValue: 2,
friction: 1,
tension: 1,
duration: this.state.timer
}
)
);
componentDidMount(){
this._runAnimation();
this.interval = setInterval(
() => this.setState((prevState)=> ({ timer: prevState.timer - 100 })),
1000
);
}
componentDidUpdate(){
if(this.state.timer === 100){
clearInterval(this.interval);
}
}
componentWillUnmount(){
clearInterval(this.interval);
}
But it won't work, speed won't change and it keeps going in start speed.
could you please help me to achieve this?
thanks.