Error while animating rotateX in react native - 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

Related

Changed from react native class to function and it doesnt work 100%

I am trying to change a class into a function. This is mainly because if I can get it working, I want to use it for learning different animations, I have had some success but not 100%. Originally it displayed an icon that when clicked it spun it one way and then when clicked again it spun the other way. What I have tried to do it get rid of the icon and replace it with an image. It works when clicked once but then does nothing.
I am struggling with toggled aspect of it and setting the state I think because I cant seem to set it up properly in a function.
I have tried several things but this is the best I can get. If I show the original code and then what I have managed to change, maybe someone can point me in the right direction as to what I am doing wrong.
All I want is the image to display and then when clicked spins right and then if clicked again it spins left.
I am doing this so I can mess around with the settings and hopefully learn animation a bit better.
Any help would be greatly appreciated.
The original code :
import React from 'react';
import { View, StyleSheet, Animated, Image, TouchableOpacity } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
const TabIcon = ({
onPress,
menuToggled
}) => {
const logoStyles = [styles.logoStyle];
if (menuToggled !== null) {
const animation = new Animated.Value(menuToggled ? 0 : 1);
Animated.timing(animation, {
toValue: menuToggled ? 1 : 0,
duration: 500,
useNativeDriver: true
}).start();
const rotateInterpolate = animation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
});
const animatedStyles = { transform: [{ rotate: rotateInterpolate }] };
logoStyles.push(animatedStyles);
}
return (
<TouchableOpacity
style={styles.tabStyle}
onPress={onPress}
>
<Animated.View style={logoStyles}>
<Animated.Image
style={styles.tinyLogo}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
}}
/></Animated.View>
</TouchableOpacity>
);
};
export default class App extends React.Component {
state = {
menuToggled: null
}
toggleMenu = () => {
this.setState(prevState => {
return { menuToggled: !prevState.menuToggled };
});
}
render () {
return (
<View style={styles.container}>
<TabIcon
onPress={this.toggleMenu}
menuToggled={this.state.menuToggled}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
},
tinyLogo: {
width: 150,
height: 150,
borderRadius: 100,
margin: 8,
},
});
and what I have changed so far :
import React, { useRef, useState } from "react";
import { View, StyleSheet, Animated, Image, TouchableOpacity, Easing } from 'react-native';
import Constants from 'expo-constants';
const App = () => {
const spinValue = useRef(new Animated.Value(0)).current;
const [menuToggled, setMenuToggled] = useState([null]);
toggleMenu = () => {
setMenuToggled(menuToggled === "null" ? "menuToggled" : "null");
}
const Spinner = ({
onPress,
menuToggled
}) => {
const logoStyles = [styles.logoStyle];
const animation = new Animated.Value(0);
const go = () => {
Animated.timing(animation, {
toValue: 1,
duration: 1500,
easing: Easing.elastic(1),
useNativeDriver: true
}).start();
}
const rotateInterpolate = animation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
});
const animatedStyles = { transform: [{ rotate: rotateInterpolate }] };
logoStyles.push(animatedStyles);
return (
<TouchableOpacity
onPress={go}
>
<Animated.View style={logoStyles}>
<Animated.Image
style={styles.tinyLogo}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
}}
/></Animated.View>
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<Spinner
onPress={toggleMenu}
menuToggled={menuToggled}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
},
tinyLogo: {
width: 150,
height: 150,
borderRadius: 100,
margin: 8,
},
});
export default App;
There are a few issues. You first had menuToggled initialized to [null] when it should have been null. You also had forgotten to use onPress in TabIcon. The most noteworthy thing was wrapping TabIcon in a useCallback to prevent it from being recreated all the time. Expo snack:
import React, { useRef, useState, useCallback } from 'react';
import {
View,
StyleSheet,
Animated,
Image,
TouchableOpacity,
} from 'react-native';
import Constants from 'expo-constants';
const App = () => {
const spinValue = useRef(new Animated.Value(0)).current;
const [menuToggled, setMenuToggled] = useState(null);
const TabIcon = useCallback(({ onPress, menuToggled }) => {
const logoStyles = [styles.logoStyle];
// initialized base on menuToggled
// if not done then it will take an additional button press to trigger
// the animation
const animation = useRef(new Animated.Value(menuToggled ? 0 : 1)).current;
const startAnimation = () => {
Animated.timing(animation, {
toValue: menuToggled ? 1 :0,
duration: 500,
useNativeDriver: true,
}).start();
};
const rotateInterpolate = animation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
});
const animatedStyles = { transform: [{ rotate: rotateInterpolate }] };
logoStyles.push(animatedStyles);
return (
<TouchableOpacity
onPress={() => {
startAnimation();
onPress?.();
}}>
<Animated.View style={logoStyles}>
<Animated.Image
style={styles.tinyLogo}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
}}
/>
</Animated.View>
</TouchableOpacity>
);
},[]);
return (
<View style={styles.container}>
<TabIcon
onPress={() => setMenuToggled((prev) => !prev)}
menuToggled={menuToggled}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
tinyLogo: {
width: 150,
height: 150,
borderRadius: 100,
margin: 8,
},
});
export default App;

ReactNative: animate backgroundColor according to ScrollView scroll position

I have a ScrollView and I want to have a View with background color white, then if scroll position is more than 0 change it to transparent, and back to white if scroll goes back to 0.
I tried to get started but react native animations seem crazy complicated coming back from a vue.js background.
Here is my code:
const [animation, setAnimation] = useState(new Animated.Value(0))
const handleScroll = (event) => {
console.log(event.nativeEvent.contentOffset.y);
var scroll = event.nativeEvent.contentOffset.y;
if(scroll > 0)
{
handleAnimation();
}
};
const boxInterpolation = animation.interpolate({
inputRange: [0, 1],
outputRange:["rgb(255,255,255)" , "rgba(255,255,255,0)"]
})
const animatedStyle = {
backgroundColor: boxInterpolation
}
const handleAnimation = () => {
Animated.timing(animation, {
toValue:1,
duration: 1000,
useNativeDriver: true
}).start();
};
And my views
<ScrollView showsVerticalScrollIndicator={false} scrollEventThrottle={16} onScroll={handleScroll} style={{flex:1,backgroundColor:'white',position:'relative'}}>
<View style={{ width:'100%', height:505,position:'relative' ,backgroundColor:'red}}>
</View>
</ScrollView>
<Animated.View style={{width:'100%',height:100,...animatedStyle, flexDirection:'row',justifyContent:'space-around',alignItems:'center',position:'absolute',bottom:0,left:0}}>
</Animated.View>
The recommend way of getting the ScrollView x and y position is this below
const scrolling = React.useRef(new Animated.Value(0)).current;
<Animated.ScrollView
onScroll={Animated.event(
[{
nativeEvent: {
contentOffset: {
y: scrolling,
},
},
}],
{ useNativeDriver: false },
)}
</Animated.View>
Now everything works heres a full example (https://snack.expo.dev/#heytony01/ca5000) must be run a phone not web. And below is the code.
import React from 'react';
import { Button,View,Animated,ScrollView } from 'react-native';
export default function App() {
const scrolling = React.useRef(new Animated.Value(0)).current;
const boxInterpolation = scrolling.interpolate({
inputRange: [0, 100],
outputRange:["rgb(255,255,255)" , "rgba(255,255,255,0)"],
})
const animatedStyle = {
backgroundColor: boxInterpolation
}
return (
<View style={{flex:1}}>
<Animated.ScrollView showsVerticalScrollIndicator={false} scrollEventThrottle={16} style={{flex:1,backgroundColor:'black',position:'relative'}}
onScroll={Animated.event(
[{
nativeEvent: {
contentOffset: {
//
y: scrolling,
},
},
}],
{ useNativeDriver: false },
)}
>
<View style={{ width:'100%', height:505,position:'relative', backgroundColor:'red'}}>
</View>
</Animated.ScrollView>
<Animated.View style={{width:'100%',height:100,...animatedStyle, flexDirection:'row',justifyContent:'space-around',alignItems:'center',position:'absolute',bottom:0,left:0,zIndex:1}}>
</Animated.View>
</View>
);
}
Also if you want to print the animated values when debugging you can do this
React.useEffect(()=>{
scrolling.addListener(dic=>console.log(dic.value))
return ()=> scrolling.removeAllListeners()
},[])

Is there any way to get the remaining time of animation in react native?

suppose i have a view which is fading in and then i call stopAnimation to stop it... at this point is there a way to get the remaining time/duration left like 2000ms or 0 if completed....
below is the example with a fadeIn view. i want to resume the animation but right now when i resume it, it takes another 5 seconds to be completed... i don't want that, when resumed i want it to complete itslef from the point where it stopped.
import React, { useRef } from "react";
import { Animated, Text, View, StyleSheet, Button, SafeAreaView } `from "react-native";`
const App = () => {
// fadeAnim will be used as the value for opacity. Initial `Value: 0`
const fadeAnim = useRef(new Animated.Value(0)).current;
const fadeIn = () => {
// Will change fadeAnim value to 1 in 5 seconds
Animated.timing(fadeAnim, {
toValue: 1,
duration: 5000
}).start();
};
const stop = () => {
fadeAnim.stopAnimation()
};
return (
<SafeAreaView style={styles.container}>
<Animated.View
style={[
styles.fadingContainer,
{
// Bind opacity to animated value
opacity: fadeAnim
}
]}
>
<Text style={styles.fadingText}>Fading View!</Text>
</Animated.View>
<View style={styles.buttonRow}>
<Button title="Fade In View" onPress={fadeIn} />
<Button title="stop" onPress={stop} />
<Button title="Resume" onPress={fadeIn} />
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center"
},
fadingContainer: {
padding: 20,
backgroundColor: "powderblue"
},
fadingText: {
fontSize: 28
},
buttonRow: {
flexBasis: 100,
justifyContent: "space-evenly",
marginVertical: 16
}
});
export default App;
You can manually calculate remaining time after calling stop():
const valueDifference = toValue - fadeAnim._startingValue;
const remainingTime = (toValue - fadeAnim._value) * (duration / valueDifference);
console.log({ remainingTime });

Animation won't start if state changed (short snack for demonstration)

Snack is here
Hello, I'm hard stuck on a silly problem and I'm becoming nut.
I just wanted to make a simple and elegant animation when a screen is focused (in a tab bar navigation). My snack works perfectly until I perform a state change in my screen. Then the animation just won't start, even though the callback from focus listener is called and executed (check logs)... WHY?
I made a button to trigger manually the animation... and it works!????
I think I made the snack clear, but if you need more information, please ask me. I beg you, please help a brother in despair.
Snack is here
If you're lazy to click the Snack:
import React, { useState, useEffect } from "react";
import { Text, View, Animated, Dimensions, StyleSheet, SafeAreaView, Button } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
function HomeScreen({navigation}) {
const initialXPos = Dimensions.get("window").height * 0.5 ;
const xPos = new Animated.Value(initialXPos);
const opacity = new Animated.Value(0);
const [change, setChange] = useState(true)
useEffect(() => {
const unsubscribe = navigation.addListener("focus", comingFromBot);
return unsubscribe;
}, []);
const comingFromBot = () => {
xPos.setValue(initialXPos);
opacity.setValue(0);
Animated.parallel([
Animated.spring(xPos, {
toValue: 100,
tension:3,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
]).start();
console.log("Animation's Fired!");
};
return (
<SafeAreaView style={{flex:1}}>
<Animated.View style={[
styles.container,
{ transform: [{ translateY: xPos }] },
{ opacity: opacity },
]}>
<Text style={{fontSize:30}}>{change ? "Home!" : "TIMMY!"}</Text>
</Animated.View>
{/* debug */}
<View style={styles.fire}>
<Button title="fire" onPress={() => comingFromBot()}/>
</View>
<View style={styles.change}>
<Button title="change" onPress={() => setChange(!change)}/>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center' },
fire:{position:"absolute", width:"100%", bottom:0},
change:{position:"absolute", width:"100%", bottom:48}
});
function SettingsScreen() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding:8 }}>
<Text>{"Go to Home tab again, and notice the animation.\n\nEXCEPT if we changed the text... WHY?\n\nBUT still works if we fire the animation with the button, but after still won't work on focus detection... HOW?\n\nWorks if you hot reload / hard reload the app... HELP?"}</Text>
</View>
);
}
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
);
}
export default function App() {
return (
<NavigationContainer>
<MyTabs />
</NavigationContainer>
);
}
It doesn't work because you're not following the rules of hooks. The following things are wrong in your code:
You're using variables from outside in useEffect hook, but passing empty dependency array
The animated values need to be in a useState or useRef hook so that they aren't recreated every render
Then the animation just won't start, even though the callback from focus listener is called and executed (check logs)... WHY?
The problem is that the callback is recreated after state update on re-render, so the callback passed to the focus listener isn't the same as what's in render anymore. And since you also don't have your animated values in state/ref, new animated values are also created while the old focus listener is referring to the old values. Basically the log you see is from an old listener and not the new one.
You should use the official eslint plugin and ensure that you fix all the warnings/errors from it so that such problems are avoided.
To fix your code, do the following changes:
const [xPos] = React.useState(() => new Animated.Value(initialXPos));
const [opacity] = React.useState(() => new Animated.Value(0));
const [change, setChange] = useState(true)
useEffect(() => {
const unsubscribe = navigation.addListener("focus", comingFromBot);
return unsubscribe;
}, [navigation, comingFromBot]);
const comingFromBot = useCallback(() => {
xPos.setValue(initialXPos);
opacity.setValue(0);
Animated.parallel([
Animated.spring(xPos, {
toValue: 100,
tension:3,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
]).start();
console.log("Animation's Fired!");
}, [xPos, opacity]);
I basically added useCallback, fix the dependency arrays, and moved the animated values to useState hook.
I finally ended with this, thanks to #satya164: Snack
I also wish I read this in documentation before.
HomeScreen's code:
// HomeScreen.js
function HomeScreen({navigation}) {
const initialXPos = Dimensions.get("window").height * 0.5 ;
const xPos = useRef(new Animated.Value(initialXPos)).current
const opacity = useRef(new Animated.Value(0)).current
const [change, setChange] = useState(true)
useEffect(() => {
const unsubscribe = navigation.addListener("focus", comingFromBot);
return unsubscribe;
}, [navigation, comingFromBot]);
const comingFromBot = useCallback(() => {
xPos.setValue(initialXPos);
opacity.setValue(0);
Animated.parallel([
Animated.spring(xPos, {
toValue: 100,
tension:3,
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
]).start();
console.log("Animation's Fired!");
}, [xPos, opacity, initialXPos ]);
return (
<SafeAreaView style={{flex:1}}>
<Animated.View style={[
styles.container,
{ transform: [{ translateY: xPos }] },
{ opacity: opacity },
]}>
<Text style={{fontSize:30}}>{change ? "Home!" : "TIMMY!"}</Text>
</Animated.View>
{/* debug */}
<View style={styles.fire}>
<Button title="fire" onPress={() => comingFromBot()}/>
</View>
<View style={styles.change}>
<Button title="change" onPress={() => setChange(!change)}/>
</View>
</SafeAreaView>
);
}

Problems with automatic image carousel when data is dynamic from an api using react native

I am implementing an image carousel which has an automatic rotation. When I implement it with static data (for example: creating a constant with an array) it works just the way I want it to.
However when I am getting the data from an api using axios, the carrosuel has a wrong behavior. The wrong behavior is as follows:
Swipe to the second image on the carousel and before moving on to the third image, go back to the first image and then go to the third image, then go to the fourth image, go back to the first image, and then go to the first image. fourth, this behavior is repeated x times.
So I think the problem is when I use axios. I attach the code of the classes that intervene in the problem that I am currently presenting.
I am using react native 0.62 with hooks and axios
HomeScreen.js
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import CategoriesScreen from "./Categories/CategoriesScreen";
import { ScrollView } from "react-native-gesture-handler";
import Carousel from "./Banner/BannerOficial";
import { axiosClient } from "../../config/axios";
export default function HomeScreen({ navigation }) {
const [banners, setBanners] = useState([]);
useEffect(() => {
getBannersAPI();
}, []);
function getBannersAPI(){
axiosClient
.get("/service/banner_available")
.then(async function (response) {
setBanners(response.data);
})
.catch(function (error) {
console.log("Error cargando los banners: ", error);
});
}
return (
<View style={{ flex: 1 }}>
<ScrollView>
<Carousel data={banners} />
<CategoriesScreen navigation={navigation} />
</ScrollView>
</View>
);
}
Carousel.js
import React, { useState, useEffect } from 'react'
import { View, Text, StyleSheet, Dimensions, FlatList, Animated } from 'react-native'
import CarouselItem from './BannerItem'
const { width, heigth } = Dimensions.get('window')
let flatList
function infiniteScroll(dataList){
const numberOfData = dataList.length
let scrollValue = 0, scrolled = 0
setInterval(function() {
scrolled ++
if(scrolled < numberOfData)
scrollValue = scrollValue + width
else{
scrollValue = 0
scrolled = 0
}
this.flatList.scrollToOffset({ animated: true, offset: scrollValue})
}, 3000)
}
const Carousel = ({ data }) => {
const scrollX = new Animated.Value(0)
let position = Animated.divide(scrollX, width)
const [dataList, setDataList] = useState(data)
useEffect(()=> {
setDataList(data)
infiniteScroll(dataList)
})
if (data && data.length) {
return (
<View>
<FlatList data={data}
ref = {(flatList) => {this.flatList = flatList}}
keyExtractor={(item, index) => 'key' + index}
horizontal
pagingEnabled
scrollEnabled
snapToAlignment="center"
scrollEventThrottle={16}
decelerationRate={"fast"}
showsHorizontalScrollIndicator={false}
renderItem={({ item }) => {
return <CarouselItem item={item} />
}}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }]
)}
/>
<View style={styles.dotView}>
{data.map((_, i) => {
let opacity = position.interpolate({
inputRange: [i - 1, i, i + 1],
outputRange: [0.3, 1, 0.3],
extrapolate: 'clamp'
})
return (
<Animated.View
key={i}
style={{ opacity, height: 10, width: 10, backgroundColor: '#595959', margin: 8, borderRadius: 5 }}
/>
)
})}
</View>
</View>
)
}
console.log('Please provide Images')
return null
}
const styles = StyleSheet.create({
dotView: { flexDirection: 'row', justifyContent: 'center'}
})
export default Carousel
CarouselItem.js
import React from "react";
import { View, StyleSheet, Text, Image, Dimensions} from 'react-native';
const { width, height} = Dimensions.get('window')
const CarouselItem = ({item}) => {
return(
<View style={styles.cardView}>
<Image style={styles.image} source = {{ uri: item.imagePath}}/>
</View>
)
}
const styles = StyleSheet.create({
cardView:{
flex:1,
width: width -20,
height: height / 7,
backgroundColor: "white",
margin: 10,
borderRadius: 10,
shadowColor: "#000",
shadowOffset: {width: 0.5, height: 0.5},
shadowOpacity: 0.5,
shadowRadius: 3,
elevation: 5,
},
image: {
width: width-20,
height: height / 3,
borderRadius: 10
}
})
export default CarouselItem