Animation from sub-component is not displayed - reactjs

Problem :
Animation is not played from the parent component.
Context :
I have two components.
The first one (Parent component):
export default function LevelSecondItem (props) {
const { Data, moveTo, Identifiant, Score, GAME } = props;
return (
<View style={styles.main_container}>
<ProgressBarLight
percentage={40}
total={3}
items={1}
label={Data.description}
/>
<View style={styles.second_container}>
<ButtonCustom text={"Commencer"} onPress={() => moveTo(Data.puzzle,Identifiant+"-"+Data.id,GAME) }/>
</View>
</View>
)
}
The second component (sub-component - child) :
const ProgressBarLight = (props) => {
const fadeAnim = useRef(new Animated.Value(0)).current // Initial value for opacity: 0
useEffect(() => {
Animated.timing(
fadeAnim,
{
toValue: 100,
duration: 10000,
useNativeDriver:false
}
).start();
}, [fadeAnim])
return (<Animated.View><Animated.Text>{fadeAnim}</Animated.Text></Animated.View>)
}
Comment :
When saving the file, react is updating the app. In this way, I'm able to see the animated value. The value is changing. But the main component is not showing the animation. I don't know why.

You need to reset the animated value to zero by using animation.setValue(0) . I was not able to find this function...
I try to use setAnimation(0) and this is thowing an error.
import React, { useState, useEffect } from 'react'
import { Text, View, StyleSheet, Animated } from 'react-native'
import {GREY80_VALUE,GREEN_VALUE} from '../GlobalConstant'
export default function CardIndicator(props) {
const { currentTotalIndex, currentTotalObject } = props;
const [animation, setAnimation] = useState(new Animated.Value(0));
const anim = ()=>{
Animated.timing(
animation,
{
toValue: 1,
duration: 1000,
useNativeDriver: false,
}
).start(()=>{
Animated.timing(
animation,
{
toValue: 0,
duration: 1000,
useNativeDriver: false,
}
)
});
}
useEffect(()=>{
animation.setValue(0)
anim()
},[currentTotalIndex])
const boxInterpolation = animation.interpolate({
inputRange: [0, 1],
outputRange:[GREEN_VALUE , GREY80_VALUE]
})
const animatedStyle = {
backgroundColor: boxInterpolation
}
return (<Animated.View style={{padding:10,borderRadius:5,...animatedStyle}}><Text style={{fontFamily:"RobotoMono-Bold",fontSize: 16}}>{currentTotalIndex+" / "+currentTotalObject}</Text></Animated.View>)
}

you can use react native animatable library it's very easy to use
just like this
import * as Animatable from 'react-native-animatable';
const FadeInView = (props) => {
return (<View><Animatable.Text animation="fadeIn">{"your text here"}</Animatable.Text></View>)
}
you can find more props of this library and use it

Related

I use UseTransition of react-spring. The whole list animates when I need only one item to be animated. What`s problem?

my problem is ItemList animation, every time when i change an item - delete for example react renders and animates the whole itemList which is unnexpected behavior
Also i`d like my items to be animated when only delete and create items, im using react-spring library
But there is also an interesting thing. If i delete items from the lowest to up gradually it works as expected but if i delete elements from top to bottom the list of items rerenders and animates fully and i don`t unredstand why.
HomePage:
import PostForm from '../components/PostForm/PostForm';
import {MemoizedToDoList} from '../components/ToDoList/ToDoList';
import { useGetToDosQuery } from '../store/rtcApi';
const HomePage = () => {
const data = useGetToDosQuery();
return (
<div>
<div className="ToDoMain">
<PostForm/>
<MemoizedToDoList items={data.data? data.data.toDos.toDos:[]} isLoading={data.isLoading}/>
</div>
</div>
)
}
export default HomePage;
ToDoList:
import React from 'react'
import { useGetToDosQuery } from '../../store/rtcApi';
import { useSelector } from 'react-redux';
import { useTransition } from 'react-spring';
import LoadingSpinner from "../LoadingSpinner/LoadingSpinner";
import ToDoItem from '../ToDoItem/ToDoItem'
import ToDoListCss from "./ToDoList.module.css";
const ToDoList = ({items, isLoading}) => {
const {toDos} = useSelector(state => state.main);
const {isAuth} = useSelector(state => state.auth);
let toDosData = [];
if(isAuth && items){
toDosData = items;
}else{
toDosData = toDos;
}
const transition = useTransition(toDosData, {
from: {x: -100, y:800, opacity: 0},
enter: {x: 0, y:0, opacity: 1},
leave: {x: 100, y: 800, opacity: 0},
keys: item => item.id,
trail: 300
});
if(isLoading)
return <LoadingSpinner scaleSet={0.5}/>;
return (
<div className={ToDoListCss.toDoList}>
{transition((style, item, key)=><ToDoItem style={style} item={item} key={key}/>)}
</div>
)
}
export const MemoizedToDoList = React.memo(ToDoList);
ToDoItem:
import React from 'react'
import { useDispatch, useSelector } from 'react-redux';
import { useRemoveToDoMutation } from "../../store/rtcApi";
import { removeToDo } from "../../store/slices/mainSlice";
import {useSpring, animated} from "react-spring";
import { BsPatchExclamationFill } from 'react-icons/bs';
import { RiDeleteBin2Line } from "react-icons/ri";
import ToDoItemCss from "./toDoItem.module.css";
const ToDoItem = ({item, style}) => {
const dispatch = useDispatch();
const {isAuth} = useSelector((state)=>state.auth);
const [ removeUserToDo ] = useRemoveToDoMutation();
const crossLineStyle = useSpring({
to: { opacity: 1, width: "65%", transform:"rotate(8deg)" },
from: { opacity: 0, width: "0%", transform:"rotate(-20deg)" },
reverse: !item.isComplete,
config: { frequency: 0.1 }
});
const onRemoveItem = React.useCallback((item) => {
if(isAuth){
return removeUserToDo(item._id);
}
dispatch(removeToDo(item.id));
}, [dispatch])
return (
<animated.div style={style} className={ToDoItemCss.toDoItem}>
<animated.div style={crossLineStyle} className={ToDoItemCss.overCrossLineAnimated}></animated.div>
<div className={ToDoItemCss.toDoItemText}>{item.title}</div>
<div className={ToDoItemCss.todoItemIconGroup}>
{item.isImportant && <div className={ToDoItemCss.todoItemImportantIcon}><BsPatchExclamationFill/></div>}
<div onClick={()=>{onRemoveItem(item)}} className='todo-item_bin-icon'><RiDeleteBin2Line/></div>
</div>
</animated.div>
)
}
export default ToDoItem;
I was trying to use memo and useCallBack but i think i don`t get how shoud i properly use it here with the RTC query and redux state.
Chunks of code from ToDoList:
const transition = useTransition(toDosData, {
from: {x: -100, y:800, opacity: 0},
enter: {x: 0, y:0, opacity: 1},
leave: {x: 100, y: 800, opacity: 0},
keys: item => item.id,
trail: 300
});
if(isLoading)
return <LoadingSpinner scaleSet={0.5}/>;
return (
<div className={ToDoListCss.toDoList}>
{transition((style, item, key)=><ToDoItem style={style} item={item} key={key}/>)}
</div>
)
export const MemoizedToDoList = React.memo(ToDoList);
and here i have used useCallback and i even dono why =))
ToDoItem
const onRemoveItem = React.useCallback((item) => {
if(isAuth){
return removeUserToDo(item._id);
}
dispatch(removeToDo(item.id));
}, [dispatch])
Here how it looks like

Framer Motion dynamic variants don't work when modifying initial properties

According to the docs I can make variant properties dynamic: https://www.framer.com/docs/animation/##dynamic-variants.
But this doesn't work when I try to make the initial properties dynamic.
For example:
import React, { useState, useEffect } from "react";
import { motion, useAnimation } from "framer-motion";
//make div appear from either bottom or right, depending on "origin" custom prop
const variant = {
hidden: (origin) =>
origin === "bottom"
? { x: 0, y: 200, opacity: 0 }
: { x: 200, y: 0, opacity: 0 },
visible: { x: 0, y: 0, opacity: 1, transition: { duration: 1 } },
};
function App() {
const [origin, setOrigin] = useState("bottom");
const controls = useAnimation();
//after 2 secs make origin "right"
useEffect(() => {
setTimeout(() => {
setOrigin("right");
}, 2000);
}, []);
//after 4 secs start the animation
useEffect(() => {
setTimeout(() => {
controls.start("visible");
}, 4000);
}, [controls]);
return (
<motion.div
style={{ width: 100, height: 50, background: "red" }}
variants={variant}
initial="hidden"
animate={controls}
custom={origin}
/>
);
}
export default App;
Here I made a dynamic variant to make a div appear from either the right or bottom, which I can control from a custom prop. Initially this custom prop is set to "bottom". After 2 secs, this is changed to "right". When I start the animation after 4 secs, I expect the div to appear from the right but it still appears from the bottom:
This is because the component is already rendered and is still the same component even if the origin prop being passed to the component has changed.
You can do two things:
Use a isVisible state variable where the render method will observe for changes and render the component when it becomes true.
function App() {
const [isVisible, setIsVisible] = useState(false);
...
//after 4 secs start the animation
useEffect(() => {
setTimeout(() => {
setIsVisible(true);
controls.start("visible");
}, 4000);
}, [controls]);
return (
isVisible && (
<motion.div
...
/>
)
);
}
DEMO
Add a key prop to the component with the origin value so that when the value changes, React will re-render the component.
function App() {
...
return (
<motion.div
key={origin}
...
/>
);
}
DEMO
2nd option may be your preferred choice if you need to toggle between the origin.

React spring useTransition state updates modifying exiting component

I'm using react-spring to animate transitions in a list of text. My animation currently looks like this:
As you can see, the text in the exiting component is also updating, when I would like it to stay the same.
Here's what I am trying:
import {useTransition, animated} from 'react-spring'
import React from 'react'
function useInterval(callback, delay) {
const savedCallback = React.useRef();
// Remember the latest callback.
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
React.useEffect(() => {
let id = setInterval(() => {
savedCallback.current();
}, delay);
return () => clearInterval(id);
}, [delay]);
}
function App() {
const [copyIndex, setCopyIndex] = React.useState(0);
const transitions = useTransition(copyIndex, null, {
from: { opacity: 0, transform: 'translate3d(0,100%,0)', position: 'absolute'},
enter: { opacity: 1, transform: 'translate3d(0,0,0)' },
leave: { opacity: 0, transform: 'translate3d(0,-50%,0)' }
});
const copyList = ["hello", "world", "cats", "dogs"];
useInterval(() => {
setCopyIndex((copyIndex + 1) % copyList.length);
console.log(`new copy index was ${copyIndex}`)
}, 2000);
return (
transitions.map(({ item, props }) => (
<animated.div style={props} key={item}>{copyList[copyIndex]}</animated.div>
))
)
}
export default App;
Any ideas on how to get this to work as desired? Thank you so much!
Let the transition to manage your elements. Use the element instead of the index. Something like this:
const transitions = useTransition(copyList[copyIndex], item => item, {
...
transitions.map(({ item, props }) => (
<animated.div style={props} key={item}>{item}</animated.div>
))

Animate back and forth rotate on logo click with react native

I'm trying to animate a menu logo to rotate when clicked. I'm successfully getting the rotation when it rotates up, but it just goes directly to 0 on rotate down instead of going through the rotate animation.
This is my component:
import React from 'react';
import { TouchableOpacity, Animated } from 'react-native';
import PropTypes from 'prop-types';
import styles from './styles';
const TabIcon = ({
route,
renderIcon,
onPress,
focused,
menuToggled,
activeTintColor,
inactiveTintColor,
}) => {
const isMenuLogo = route.params && route.params.navigationDisabled;
const animation = new Animated.Value(0);
Animated.timing(animation, {
toValue: menuToggled ? 1 : 0,
duration: 200,
useNativeDriver: true,
}).start();
const rotateInterpolate = animation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '180deg'],
});
const animatedStyles = { transform: [{ rotate: rotateInterpolate }] };
const logoStyles = [animatedStyles, styles.logoStyle];
return (
<TouchableOpacity
style={styles.tabStyle}
onPress={onPress}
activeOpacity={isMenuLogo && 1}
>
<Animated.View style={isMenuLogo ? logoStyles : null}>
{
renderIcon({
route,
focused,
tintColor: focused
? activeTintColor
: inactiveTintColor,
})
}
</Animated.View>
</TouchableOpacity>
);
};
TabIcon.propTypes = {
route: PropTypes.shape({
key: PropTypes.string,
}).isRequired,
renderIcon: PropTypes.func.isRequired,
onPress: PropTypes.func,
focused: PropTypes.bool,
menuToggled: PropTypes.bool,
activeTintColor: PropTypes.string.isRequired,
inactiveTintColor: PropTypes.string.isRequired,
};
TabIcon.defaultProps = {
onPress: () => {},
focused: false,
menuToggled: false,
};
export default TabIcon;
I'm checking first if it has been toggled before actually rotating it. This component is being called in another parent component displaying a custom bottom tab navigation.
Should I be doing a different animation for it when it rotates down or am I missing a configuration in my current animation?
Any help and suggestion would really be much appreciated. Thank you.
I think the issue is related to the fact that when you set the initial value of animation because it is always set to 0 it doesn't reflect the change when you switch the menu.
You need to change:
const animation = new Animated.Value(0);
to
const animation = new Animated.Value(menuToggled ? 0 : 1);
Though making that change will cause a different problem. Because the menuToggled affects the start and end positions of the animation the Icon will now rotate into the correct starting position from the end position. This is not ideal.
However we can fix that by setting a default value of null for menuToggled. Then wrapping the animation in an if-statement that only runs if the menuToggled is not null.
Here is an example based off of your initial code:
import React from 'react';
import { View, StyleSheet, Animated, 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: 200,
useNativeDriver: true
}).start();
const rotateInterpolate = animation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '180deg']
});
const animatedStyles = { transform: [{ rotate: rotateInterpolate }] };
logoStyles.push(animatedStyles);
}
return (
<TouchableOpacity
style={styles.tabStyle}
onPress={onPress}
>
<Animated.View style={logoStyles}>
<Ionicons name="md-checkmark-circle" size={32} color="green" />
</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>
);
}
}
I stripped down your TabIcon component as there was a lot of things there that were not related to the animation. You should be easily able to incorporate what I have done into your own component. https://snack.expo.io/#andypandy/rotating-icon
I've tried Andrew's solution above and it works, but I have opted for turning it into a class component. It works the same way. See the component below.
import React, { PureComponent } from 'react';
import { TouchableOpacity, Animated } from 'react-native';
import PropTypes from 'prop-types';
import styles from './styles';
class TabIcon extends PureComponent {
constructor(props) {
super(props);
this.state = {
animation: new Animated.Value(0),
};
}
render() {
const { animation } = this.state;
const {
route,
renderIcon,
onPress,
focused,
menuToggled,
activeTintColor,
inactiveTintColor,
} = this.props;
const isMenuLogo = route.params && route.params.navigationDisabled;
Animated.timing(animation, {
toValue: menuToggled ? 1 : 0,
duration: 200,
useNativeDriver: true,
}).start();
const rotateInterpolate = animation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '180deg'],
});
const animatedStyles = { transform: [{ rotate: rotateInterpolate }] };
const logoStyles = [animatedStyles, styles.logoStyle];
return (
<TouchableOpacity
style={styles.tabStyle}
onPress={onPress}
activeOpacity={isMenuLogo && 1}
>
<Animated.View style={isMenuLogo ? logoStyles : null}>
{
renderIcon({
route,
focused,
tintColor: focused
? activeTintColor
: inactiveTintColor,
})
}
</Animated.View>
</TouchableOpacity>
);
}
}
TabIcon.propTypes = {
route: PropTypes.shape({
key: PropTypes.string,
}).isRequired,
renderIcon: PropTypes.func.isRequired,
onPress: PropTypes.func,
focused: PropTypes.bool,
menuToggled: PropTypes.bool,
activeTintColor: PropTypes.string.isRequired,
inactiveTintColor: PropTypes.string.isRequired,
};
TabIcon.defaultProps = {
onPress: () => {},
focused: false,
menuToggled: false,
};
export default TabIcon;

React native Animated API not working with recompose

I have a HOC made in recompose that isn't behaving properly - it just shows the component at it's end value without ever animating. The same component, written as a regular class, behaves fine. Can someone tell me what is causing this issue, or how I would approach this so it works correctly?
recompose HOC component:
const enhancer = compose(
withState('slideAnim', 'setSlide', new Animated.Value(width)),
withState('fadeAnim', 'setFadeAnim', new Animated.Value(0)),
lifecycle({
componentDidMount () {
Animated.parallel([
Animated.timing(this.props.slideAnim, {
toValue: 0,
duration: 500
}),
Animated.timing(this.props.fadeAnim, {
toValue: 1,
duration: 500
})
]).start()
}
})
)
const ModalScene = ({ children, slideAnim, fadeAnim }) => {
return (
<Animated.View style={[styles, { opacity: fadeAnim, left: slideAnim }]}>
{children}
</Animated.View>
)
}
regular class:
class ModalScene extends React.Component {
constructor (props) {
super(props)
this.state = {
slideAnim: new Animated.Value(width),
fadeAnim: new Animated.Value(0)
}
}
componentDidMount () {
Animated.parallel([
Animated.timing(this.state.slideAnim, {
toValue: 0,
duration: 500
}),
Animated.timing(this.state.fadeAnim, {
toValue: 1,
duration: 500
})
]).start()
}
render () {
return (
<Animated.View
style={[
styles,
{ opacity: this.state.fadeAnim, left: this.state.slideAnim }
]}
>
{this.props.children}
</Animated.View>
)
}
}
Your code is correct, you just forget to export HOC, try this:
export default enhancer(ModalScene);
I had a similar problem which seemed to be caused by the mutable nature of the Animated objects. To make sure an instance of Animated.Value was created for each instance of my components I had to use the other form of withState, the one that takes a function as initial value. Try changing these lines:
withState('slideAnim', 'setSlide', new Animated.Value(width)),
withState('fadeAnim', 'setFadeAnim', new Animated.Value(0)),
to
withState('slideAnim', 'setSlide', () => new Animated.Value(width)),
withState('fadeAnim', 'setFadeAnim', () => new Animated.Value(0)),

Resources