UI blocked after displaying a drop down alert - react-native

I'm trying to display the error message via a drop down alert which I have hooked onto redux. I have it in the the main App.js file and the component is listening to error state change and will drop via an animation to show an error message. The Issue is once it has displayed the error message and went back up, I'm not able to click on the back button.
I tried to move the component after and before the SwitchNavigator.
// Drop down component
const DropDownAlert = props => {
const containerHeight = 120;
const ytranslate = new Animated.Value(-containerHeight);
useEffect(() => {
Animated.sequence([
Animated.spring(ytranslate, {
toValue: 0, //Animate to YPosition: 20
useNativeDriver: true,
easing: Easing.linear,
friction: 100,
tension: 0
}),
Animated.spring(ytranslate, {
delay: 3000, //waits 3s
toValue: -(containerHeight + 25), //Animate to YPosition: 20
useNativeDriver: true,
easing: Easing.linear,
friction: 100,
tension: 0
})
]).start(() => {props.removeAlertMessage});
});
if(!props.alertMessage.isMessage){
return null
}
return (
<View style={styles.container}>
<Animated.View
style={{
...props.style,
transform: [{ translateY: ytranslate }],
opacity: 0.8,
borderWidth: StyleSheet.hairlineWidth,
borderColor: theme.SECONDARY_COLOR
}}
>
<View style={styles.content}>
<View style={styles.iconContainer}>
<MaterialCommunityIcons
name="alert-circle-outline"
size={32}
color={theme.TERTIARY_COLOR}
/>
</View>
<Text style={styles.text}>{props.alertMessage.message}</Text>
</View>
</Animated.View>
</View>
);
};
// app.js return statement
return (
<Provider store={store}>
<AlertMessage/>
<SwitchNavigator uriPrefix={prefix} />
</Provider>
);
So I would like to be able to show the error message in the drop down alert and once it went back up I should be able to click on the back button.

Related

Smooth rotation of React Native SVG Component [duplicate]

Rotation is a style transform and in RN, you can rotate things like this
render() {
return (
<View style={{transform:[{rotate: '10 deg'}]}}>
<Image source={require('./logo.png')} />
</View>
);
}
However, to animate things in RN, you have to use numbers, not strings. Can you still animate transforms in RN or do I have to come up with some kind of sprite sheet and change the Image src at some fps?
You can actually animate strings using the interpolate method. interpolate takes a range of values, typically 0 to 1 works well for most things, and interpolates them into a range of values (these could be strings, numbers, even functions that return a value).
What you would do is take an existing Animated value and pass it through the interpolate function like this:
spinValue = new Animated.Value(0);
// First set up animation
Animated.timing(
this.spinValue,
{
toValue: 1,
duration: 3000,
easing: Easing.linear, // Easing is an additional import from react-native
useNativeDriver: true // To make use of native driver for performance
}
).start()
// Next, interpolate beginning and end values (in this case 0 and 1)
const spin = this.spinValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
Then use it in your component like this:
<Animated.Image
style={{transform: [{rotate: spin}] }}
source={{uri: 'somesource.png'}} />
In case if you want to do the rotation in loop, then add the Animated.timing in the Animated.loop
Animated.loop(
Animated.timing(
this.spinValue,
{
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true
}
)
).start();
Don't forget to add property useNativeDriver to ensure that you get the best performance out of this animation:
// First set up animation
Animated.timing(
this.state.spinValue,
{
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true
}
).start();
A note for the newbies like me:
For animating something else you need to wrap it in <Animated.SOMETHING> for this to work. Or else the compiler will panic on that transform property:
import {Animated} from 'react-native';
...
//animation code above
...
<Animated.View style={{transform: [{rotate: spinValue}] }} >
<YourComponent />
</Animated.View>
BUT for an image (Animated.Image), the example above is 100% goodness and correct.
Since most of the answers are functions & hooks based, herewith a complete example of class based Animation of Image.
import React from 'react';
import {
SafeAreaView,
View,
Animated,
Easing,
TouchableHighlight,
Text,
} from 'react-native';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
rotateValueHolder: new Animated.Value(0)
};
}
componentDidMount = () => {
this.startImageRotateFunction();
}
startImageRotateFunction = () => {
Animated.loop(Animated.timing(this.state.rotateValueHolder, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: false,
})).start();
};
render(){
return(
<SafeAreaView>
<View>
<Animated.Image
style={{
width: 200,
height: 200,
alignSelf:"center",
transform:
[
{
rotate: this.state.rotateValueHolder.interpolate(
{
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
}
)
}
],
}}
source={{uri:'https://raw.githubusercontent.com/AboutReact/sampleresource/master/old_logo.png',}}
/>
<TouchableHighlight
onPress={() => this.startImageRotateFunction()}>
<Text style={{textAlign:"center"}}>
CLICK HERE
</Text>
</TouchableHighlight>
</View>
</SafeAreaView>
);
}
}
Just gonna drop the solution I solved by stitching together parts from the answers here.
import { Feather } from '#expo/vector-icons'
import * as React from 'react'
import { TextStyle, Animated, Easing } from 'react-native'
import { Colors, FontSize } from '~/constants/Theme'
export const LoadingSpinner = React.memo(
({ color = Colors['sand'], size = FontSize['md'] - 1, fadeInDelay = 1000, ...props }: Props) => {
const fadeInValue = new Animated.Value(0)
const spinValue = new Animated.Value(0)
Animated.sequence([
Animated.delay(fadeInDelay),
Animated.timing(fadeInValue, {
toValue: 1,
duration: 1500,
easing: Easing.linear,
useNativeDriver: true,
}),
]).start()
Animated.loop(
Animated.timing(spinValue, {
toValue: 360,
duration: 300000,
easing: Easing.linear,
useNativeDriver: true,
})
).start()
return (
<Animated.View
style={{
opacity: fadeInValue,
transform: [{ rotate: spinValue }],
}}
>
<Feather
name="loader"
size={size}
style={{
color,
alignSelf: 'center',
}}
{...props.featherProps}
/>
</Animated.View>
)
}
)
type Props = {
color?: TextStyle['color']
size?: number
featherProps?: Partial<Omit<React.ComponentProps<typeof Feather>, 'style'>>
fadeInDelay?: number
}
Hope it helps šŸ‘

Error while animating rotateX in react native

I am trying to rotate an icon on press of a button in my react native application.
But I am getting this error:
Error while updating property 'transform' of a view managed by:
RCTView
This is the Icon itself:
<TouchableOpacity
style={[
styles.expandButton,
{transform: [{rotateX: toString(expandIconAngle) + 'deg'}]},
]}
onPress={() => {
rotateIcon();
}}>
<Icon name="expand-less" color="#ffffff" size={28} />
</TouchableOpacity>
This is the function which is responsible for rotating the icon:
const expandIconAngle = useRef(new Animated.Value(0)).current;
function rotateIcon() {
Animated.timing(expandIconAngle, {
toValue: 180,
duration: 300,
easing: Easing.linear,
}).start();
}
Where am I going wrong?
use interpolate and Animated.Image :
import React, { useRef } from "react";
import { Animated, Text, View, StyleSheet, Button, SafeAreaView,Easing,TouchableOpacity } from "react-native";
const App = () => {
// fadeAnim will be used as the value for opacity. Initial Value: 0
const angle = useRef(new Animated.Value(0)).current;
const fadeOut = () => {
// Will change fadeAnim value to 0 in 3 seconds
Animated.timing(
angle,
{
toValue: 1,
duration: 3000,
easing: Easing.linear, // Easing is an additional import from react-native
useNativeDriver: true // To make use of native driver for performance
}
).start()
};
const spin =angle.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
return (
<SafeAreaView style={styles.container}>
<Animated.Image
style={{transform: [{rotateX: spin}] }}
source={require('#expo/snack-static/react-native-logo.png')} />
<TouchableOpacity onPress={fadeOut} style={styles.buttonRow}>
<Text>Button</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center"
},
buttonRow: {
flexBasis: 100,
justifyContent: "space-evenly",
marginVertical: 16
}
});
export default App;
LIVE example - https://snack.expo.dev/TP-fQExXT

Component Re-renders onChangeText

I made a custom TextInput that I'm animating into view on my Registration screen component. The issue is that for every keystroke onChangeText, the animation plays again and this is not the expected behavior. If I take out the animation, the TextInput works fine.
I've tried changing my dependency on my useState, also tried to wrap the component with a useCallback and useMemo and none has gotten it to work.
Also worthy of note is that I'm handling my state management with useReducer. Code below
const RegistrationScreen = ({ navigation }: any) => {
const textPosition = new Animated.Value(width);
const inputPosition = new Animated.Value(width);
const inputPosition2 = new Animated.Value(width);
const inputPosition3 = new Animated.Value(width);
const inputPosition4 = new Animated.Value(width);
const [state, dispatch] = useReducer(reducer, initialState);
const { name, email_address, phone_number, password } = state;
const animate = Animated.sequence([
Animated.parallel([
Animated.timing(textPosition, {
toValue: 0,
delay: 50,
duration: 1000,
useNativeDriver: true,
easing: Easing.elastic(3),
}),
Animated.timing(inputPosition, {
toValue: 0,
delay: 100,
duration: 1000,
useNativeDriver: true,
easing: Easing.elastic(3),
}),
Animated.timing(inputPosition2, {
toValue: 0,
delay: 200,
duration: 1000,
useNativeDriver: true,
easing: Easing.elastic(3),
}),
Animated.timing(inputPosition3, {
toValue: 0,
delay: 300,
duration: 1000,
useNativeDriver: true,
easing: Easing.elastic(3),
}),
Animated.timing(inputPosition4, {
toValue: 0,
delay: 400,
duration: 1000,
useNativeDriver: true,
easing: Easing.elastic(3),
}),
]),
]);
const _onCreateAccountHandler = () => dispatch(getPhoneVerificationCode(phone_number));
const _onChangeHandler = (field: any, value: any) => dispatch({ type: 'FIELD', field, value });
useEffect(() => {
animate.start();
}, [animate]);
return (
<SafeAreaView style={{ flex: 1 }}>
<KeyboardAvoidingView
style={{ flex: 1 }}
keyboardVerticalOffset={100}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
<View style={styles.container}>
<View style={{ flex: 1 }}>
<Header
backButtonEnabled
backButtonColor={colors.darkGray}
onBackButtonPress={() => navigation.goBack(null)}
/>
</View>
<View style={styles.innerContainer}>
<Animated.View
style={[
styles.createAccount,
{
transform: [
{
translateX: textPosition,
},
],
},
]}>
<Text style={styles.creaetAccountText}>{strings.create_an_account}</Text>
</Animated.View>
<View style={styles.textAreaContainer}>
<Animated.View
style={[
styles.textInputContainer,
{
transform: [
{
translateX: inputPosition,
},
],
},
]}>
<TextInput
placeHolder={strings.name}
value={name}
onChangeText={(text: any) => _onChangeHandler('name', text)}
onCancelPressed={() => {}}
placeHolderStyle={{
backgroundColor: colors.lightWhite,
}}
autoCorrect={false}
/>
</Animated.View>
You could try wrapping animate with React.useCallback()
So From further research, it turns out that since I'm animating the component with a value that is always constant (width), onTextChange will always rerender the screen and any value that is different will be accounted for, hence, the animation plays again since the current value is different from the initial value.
My Solution:
I had to use useState to change the initial value as soon as the first animation completes. I also had to call that in a setTimeout that runs after the animation that way - the initial width will update to become the current width and when the component re-renders, it won't render again. (or in this case, there's no difference in the translate X value, so animation won't play again)

Scale an image with Animated react native expo is not applying permanently

Iā€™m trying to increase the size of an image on user press and decrease it when he presses again with animated API using the following:
const [viewState, setViewState]= useState(true);
const scaleAnim = (new Animated.Value(.9))
const scaleOut = () => {
if(viewState){
Animated.timing(scaleAnim, {
toValue: 2.2,
duration: 2000,
useNativeDriver:true,
}).start(()=>{setViewState(false)});
}
else{
Animated.timing(scaleAnim, {
toValue: .9,
duration: 700,
useNativeDriver:true,
}).start(setViewState(true));
}
};
<Animated.View style={{transform:[{scale:scaleAnim}]}} >
<Image style={styles.image} source={require('../path..')} />
</Animated.View>
const styles = StyleSheet.create({
image: {
width:70,
resizeMode:"contain",
height: 45,
alignSelf: "center",
},
But the issue is, whenever the duration is over, the size is going back to default. I want to to stay permanently and do the opposite when the user presses again(decrease size)
Any suggestions?
Created a Component hope this is how you wanted....
snack: https://snack.expo.io/neEtc2ihJ
export default function App() {
const [viewState, setViewState] = React.useState(true);
const scale = React.useRef(new Animated.Value(1)).current;
const [init, setInit] = React.useState(true);
React.useEffect(() => {
if (init) {
setInit(false);
} else {
if (viewState) {
Animated.timing(scale, {
toValue: 2,
duration: 1000,
useNativeDriver: true,
}).start();
} else {
Animated.timing(scale, {
toValue: 0.5,
duration: 700,
useNativeDriver: true,
}).start();
}
}
}, [viewState]);
const scaleOut = () => {
setViewState(!viewState);
};
return (
<View style={styles.container}>
<Animated.View style={{ transform: [{ scale }] }}>
<Image
style={styles.image}
source={require('./assets/snack-icon.png')}
/>
</Animated.View>
<Button title="animate" onPress={scaleOut} />
</View>
);
}
Firstly you want your animated value to either useState or useRef. The react-native example uses useRef, so I'd suggest you to do the same. I'd also suggest tha you use an interpolation to scale so that you can tie more animations to that one animated value. The result would be something like this:
const animatedValue = useRef(new Animated.Value(0)).current;
const [ toggle, setToggle ] = useState(false)
const scaleOut = () => {
let animation
if(!toggle){
animation = Animated.timing(animatedValue, {
toValue: 1,
duration: 700,
useNativeDriver:true,
});
}
else{
animation = Animated.timing(animatedValue, {
toValue: 0,
duration: 2000,
useNativeDriver:true,
});
}
animation.start(()=>{
setToggle(!toggle)
})
};
let scaleAnim = animatedValue.interpolate({
inputRange:[0,1],
outputRange:[0.9,2.2]
})
return (
<Animated.View style={{transform:[{scale:scaleAnim}]}} >
<TouchableOpacity onPress={scaleOut}>
<Image style={styles.image} source={require('../path..')} />
</TouchableOpacity>
</Animated.View>
);
By doing this, you can scale multiple images at whatever size you want by just adding another interpolation. But if you have no desire to do that:
const scaleOut = () => {
let animation
if(!toggle){
animation = Animated.timing(animatedValue, {
toValue: 2.2,
duration: 2000,
useNativeDriver:true,
});
}
else{
animation = Animated.timing(animatedValue, {
toValue: 0.9,
duration: 700,
useNativeDriver:true,
});
}
animation.start(()=>{
setToggle(!toggle)
})
};
return (
<Animated.View style={{transform:[{scale:animatedValue}]}} >
<TouchableOpacity onPress={scaleOut} />
<Image style={styles.image} source={require('../path..')} />
</TouchableOpacity>
</Animated.View>
);
If you want to go a step further, swap out the TouchableOpacity for a Pressable, put the animations in a Animated.loop and start that in onPressIn, and on pressOut stop the animations and bring the set the animatedValue back to initial value:
const onPressIn= ()=>{
Animated.loop([
Animated.timing(animatedValue, {
toValue: 2.2,
duration: 2000,
useNativeDriver:true,
}),
Animated.timing(animatedValue, {
toValue: 0.9,
duration: 700,
useNativeDriver:true,
});
],{useNativeDriver:true}).start()
}
const onPressOut= ()=>{
animatedValue.stop()
Animated.timing(animatedValue,{
toValue: 0.9,
duration: 700,
useNativeDriver:true,
})
}
return(
<Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
<Animated.View style={{transform:[{scale:animatedValue}]}} >
<Image style={styles.image} source={require('../path..')} />
</Animated.View>
</Pressable>
);

react native animated repeat infinity

I have the following animation.
componentWillMount(){
this.animatedValue = new Animated.Value(300);
}
componentDidMount(){
Animated.timing( this.animatedValue , {
toValue: -100,
duration: 3000,
} ).start();
}
render() {
const animatedStyle = { marginLeft: this.animatedValue, marginRight: - this.animatedValue };
return (
<View style={{flexDirection: 'row', height: 100,}}>
<Animated.View style={[ animatedStyle,{backgroundColor: 'blue', width:100}]} />
</View>
);
}
I would like to repeat endless times. Anyone have any suggestions?
2019 solution:
Animated.loop(Animated.timing(this.animatedValue , {
toValue: -100,
duration: 3000,
})).start();
Pass a callback to Animated.start() that resets the Animated value and starts the animation again. For example:
componentDidMount(){
this.runAnimation();
}
runAnimation() {
this.animatedValue.setValue(300);
Animated.timing(this.animatedValue, {
toValue: -100,
duration: 3000,
}).start(() => this.runAnimation());
}
If you need to stop the animation at any point, take a look at this question/answer.