1

Scenario: I have an svg circle and a state rendered on the screen. I also have two buttons. Change Size button changes the size (shared value) of the circle from 50 to 100 or 100 to 50. Change State button changes the state from 'apple' to 'orange' or 'orange' to 'apple'. [Note: Animation does not use the state in any way. I am also console logging the size.value in every rerender]

Issue: Once the Change Size button is pressed, it animates the circle from 50 to 100. Now if you press Change State button, it changes the state but it also makes the size of the circle back to 50 although our log shows that the shared value size.value is still 100.

Expected Behavior: Expected the size of the circle to remain 100 as that is the shared value supplied to the circle.

Code:

import React, {useState, useEffect} from 'react';
import { Text, View, Button} from 'react-native';
import Animated, {
  useAnimatedProps,
  useSharedValue,
  withSpring,
} from 'react-native-reanimated';
import Svg, {Circle} from 'react-native-svg';

const App = () => {
  const [state, setState] = useState('apple');

  const size = useSharedValue(50);
  const animatedProps = useAnimatedProps(() => {
    return {
      r: size.value / 2,
    };
  });
  const AnimatedCircle = Animated.createAnimatedComponent(Circle);

  useEffect(() => {
    console.log('size.value =', size.value);
  });

  return (
    <View style={{flex: 1}}>
      <Svg height={100} width={100}>
        <AnimatedCircle
          cx="50"
          cy="50"
          fill="green"
          animatedProps={animatedProps}
        />
      </Svg>
      <Text>{state}</Text>
      <Button
        title="Change Size"
        onPress={() => {
          size.value = withSpring(size.value === 50 ? 100 : 50);
        }}
      />
      <Button
        title="Change State"
        onPress={() => {
          setState(state === 'apple' ? 'orange' : 'apple');
        }}
      />
    </View>
  );
}

export default App;

Any help would be much appreciated

bgcodes
  • 248
  • 2
  • 12

2 Answers2

7

Simply move the const AnimatedCircle = Animated.createAnimatedComponent(Circle); to outside of your function component. Because, on every render react runs your function. And since createAnimatedComponent is in your function body, it re-runs too, so it creates the component again from scratch. But you should be creating the component once.

import React, {useState, useEffect} from 'react';
import { Text, View, Button} from 'react-native';
import Animated, {
  useAnimatedProps,
  useSharedValue,
  withSpring,
} from 'react-native-reanimated';
import Svg, {Circle} from 'react-native-svg';

const AnimatedCircle = Animated.createAnimatedComponent(Circle);

const App = () => {
  const [state, setState] = useState('apple');

  const size = useSharedValue(50);
  const animatedProps = useAnimatedProps(() => {
    return {
      r: size.value / 2,
    };
  });

  useEffect(() => {
    console.log('size.value =', size.value);
  });

  return (
    <View style={{flex: 1}}>
      <Svg height={100} width={100}>
        <AnimatedCircle
          cx="50"
          cy="50"
          fill="green"
          animatedProps={animatedProps}
        />
      </Svg>
      <Text>{state}</Text>
      <Button
        title="Change Size"
        onPress={() => {
          size.value = withSpring(size.value === 50 ? 100 : 50);
        }}
      />
      <Button
        title="Change State"
        onPress={() => {
          setState(state === 'apple' ? 'orange' : 'apple');
        }}
      />
    </View>
  );
}
Ugur Eren
  • 1,968
  • 2
  • 11
  • 21
0

On state update the functional component gets re-rendered and the local variables values are reinitialised. Instead of keeping UI component(Circle) in parent which doesn't need state directly, we can move it to different component and wrap with React.memo. React.memo re-renders the wrapped component only when the props are updated. In this case when the state is updated the props in the ui components will remain same and It won't re-render. Lets move the circle component code to new component.

import React, {useState, useEffect} from 'react';
import { Text, View, Button} from 'react-native';
import Animated, {
  useAnimatedProps,
  useSharedValue,
  withSpring,
} from 'react-native-reanimated';
import Svg, {Circle} from 'react-native-svg';

const App = () => {
  const [state, setState] = useState('apple');
  const size = useSharedValue(50);

  useEffect(() => {
    console.log('size.value =', size.value);
  });

  return (
    <View style={{flex: 1}}>
      
      <Text>{state}</Text>
enter code here
enter code here
      <CircleMemo size={size}/>
      <Button
        title="Change Size"
        onPress={() => {
          size.value = withSpring(size.value === 50 ? 100 : 50);
        }}
      />
      <Button
        title="Change State"
        onPress={() => {
          setState(state === 'apple' ? 'orange' : 'apple');
        }}
      />
    </View>
  );
}

export default App;

Now memoise the React component with React.memo so that it doesn't re-render on state update.

const CircleUI = ({size})=>{
      const animatedProps = useAnimatedProps(() => {
        return {
          r: size.value / 2,
        };
      });
      const AnimatedCircle = Animated.createAnimatedComponent(Circle);
      return (
          <Svg height={100} width={100}>
            <AnimatedCircle
              cx="50"
              cy="50"
              fill="green"
              animatedProps={animatedProps}
            />
          </Svg>
     )
    
    }
const CircleMemo = React.memo(CircleUI);
Aman Rathore
  • 88
  • 1
  • 11