Search code examples
reactjsreact-nativeanimationreact-animated

React Native Animated don't work on first click


I am creating a poker table using react native and I want that on the click of a chair animation should happen(arrangement of chairs around the table).

But animation does not work on clicking on the first time, while it works when I click 2nd time.

Please check the image belowenter image description here

You can see there are 8 chairs around the table what I want is that whenever I click on any table, the rotation should take place in such a way that the table I clicked on should replace the green table(bottom)

The problem is that on the first click, the code inside useEffect works but still animation does not occur.

Here is the code

import {
  Animated,
} from 'react-native';

function HomeScreen() {
  const [shiftBy, setSiftBy] = React.useState(0);
  const [p1InitialPosition] = React.useState(new Animated.ValueXY({ x: 180, y: 30 }));
  const [p2InitialPosition] = React.useState(new Animated.ValueXY({ x: 325, y: 125 }));
  const [p3InitialPosition] = React.useState(new Animated.ValueXY({ x: 338, y: 230 }));
  const [p4InitialPosition] = React.useState(new Animated.ValueXY({ x: 335, y: 370 }));
  const [p5InitialPosition] = React.useState(new Animated.ValueXY({ x: 180, y: 470 }));
  const [p6InitialPosition] = React.useState(new Animated.ValueXY({ x: 30, y: 370 }));
  const [p7InitialPosition] = React.useState(new Animated.ValueXY({ x: 30, y: 230 }));
  const [p8InitialPosition] = React.useState(new Animated.ValueXY({ x: 40, y: 125 }));

  const chairs = React.useMemo(() => {
    return [
      p1InitialPosition,
      p2InitialPosition,
      p3InitialPosition,
      p4InitialPosition,
      p5InitialPosition,
      p6InitialPosition,
      p7InitialPosition,
      p8InitialPosition,
    ];
  }, [
    p1InitialPosition,
    p2InitialPosition,
    p3InitialPosition,
    p4InitialPosition,
    p5InitialPosition,
    p6InitialPosition,
    p7InitialPosition,
    p8InitialPosition,
  ]);

  // updated/animated positions

  React.useEffect(() => {
    const np = [];
    for (let i = 0; i < chairs.length; i++) {
      console.log(chairs[i], chairs[(i + shiftBy) % 8]);
      np.push(
        Animated.timing(chairs[i], {
          toValue: chairs[(i + shiftBy) % 8],
          duration: 800,
          useNativeDriver: true,
        }),
      );
    }
    Animated.parallel(np).start();

    console.log('');
  }, [shiftBy]);
  return (
    <SafeAreaView style={styles.container}>
      <Image
        onLayout={event => {
          const layout = event.nativeEvent.layout;
          console.log('height:', layout.height);
          console.log('width:', layout.width);
          console.log('x:', layout.x);
          console.log('y:', layout.y);
        }}
        source={require('../../assets/image/table/table.png')}
        style={{
          height: '100%',
          width: '100%',
        }}
      />
      {/* p1 */}
      <Animated.View
        style={{
          position: 'absolute',
          transform: [{ translateX: p1InitialPosition.x }, { translateY: p1InitialPosition.y }],
        }}>
        <TouchableOpacity
          // disabled={selectedChair}
          onPress={() => setSiftBy(4)}>
          <Icon name="chair" size={30} color="blue" />
        </TouchableOpacity>
      </Animated.View>
      {/* p2 */}
      <Animated.View
        style={{
          position: 'absolute',
          transform: [{ translateX: p2InitialPosition.x }, { translateY: p2InitialPosition.y }],
        }}>
        <TouchableOpacity
          // disabled={selectedChair}
          onPress={() => setSiftBy(3)}>
          <Icon name="chair" size={30} color="skyblue" />
        </TouchableOpacity>
      </Animated.View>
      {/* p3 */}
      {/* p4 */}
      {/* p5 */}
      {/* p6 */}
      {/* p7 */}
      {/* p8 */}
    </SafeAreaView>
  );
}

export default HomeScreen;

this is the link to complete the code for the screen- snack.expo.dev/sd7yF5CzZ

Please help.


Solution

  • The solution is to add useLayoutEffect in your code :

    React.useLayoutEffect(() => {
        const np = [];
        for (let i = 0; i < chairs.length; i++) {
          console.log(chairs[i], chairs[(i + shiftBy) % 8]);
          np.push(
            Animated.timing(chairs[i], {
              toValue: chairs[(i + shiftBy) % 8],
              duration: 800,
              useNativeDriver: true,
            }),
          );
        }
        Animated.parallel(np).start();
    
        console.log('');
      }, [shiftBy]);
    

    Explaination

    Since useEffect callbacks are called after the render commit phase and hence after the render cycle, and LayoutAnimation.configureNext is meant to "prepare" the next render, it is already too late to call it inside of useEffect.

    So the solution is to use useLayoutEffect along with useEffect to ensure that animation will run on first render