Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fix React-Native animation to only animate "this" component

I have a Tile class in a tic-tac-toe game. my goal is to animate the appearance of the tile contents, and only the appearance of the most recently pressed tile. my problem is two-fold: 1: the animation only works for the first tile press, and not every time 2: the animation affects all of the tiles

EDIT: added value.setValue(0); to useEffect() to reset animated value each press. The only problem that remains is that all the Tile components animate on each press, whereas I only expect an animation for the <Tile> component who's props.contents changed.

If I instead put the Animated.timing function inside onSelection, then only the pressed animates as expected, but since this is over a socket connection, both client's tiles need to animate, and in this case, only the client who pressed the Tile sees the animation. (because the animation function is no longer in useEffect() )

EDIT 2: Tried changing useEffect() to include dependency array and a conditional statement as well as just including the dependency array as referenced in https://reactjs.org/docs/hooks-effect.html

Tile.js

import React, { useEffect, useState } from "react";
import SocketContext from "../socket-context";
import { View, Animated, TouchableOpacity } from "react-native";
import styles from "../assets/styles/main-style";

function Tile(props) {
  function onSelection(row, col, socket) {
    props.onSelection(row, col, socket);
  }
  let [value] = useState(new Animated.Value(0));

  // useEffect(() => {
  //  value.setValue(0);
  //  Animated.timing(value, {
  //    toValue: 100,
  //    duration: 10000,
  //    useNativeDriver: true
  //  }).start();
  // });

  (EDIT 2-a):
   useEffect(() => {
    if (props.contents !== {marker: null}) {
      return
    } else {
     value.setValue(0);
     Animated.timing(value, {
       toValue: 100,
       duration: 10000,
       useNativeDriver: true
     }).start();
    }
   }, [props.contents]);

  (EDIT 2-b):
   useEffect(() => {
     value.setValue(0);
     Animated.timing(value, {
       toValue: 100,
       duration: 10000,
       useNativeDriver: true
     }).start();
   }, [props.contents]);

  let animated_opacity = value.interpolate({
    inputRange: [0, 100],
    outputRange: [0.0, 1.0]
  });

  let animated_rotation = value.interpolate({
    inputRange: [0, 50, 100],
    outputRange: ["0deg", "180deg", "360deg"]
  });

  return (
    <SocketContext.Consumer>
      {socket => (
        <View style={styles.grid_cell}>
          <TouchableOpacity
            style={styles.cell_touchable}
            onPress={() => onSelection(props.row, props.col, socket)}
          >
            <Animated.Text
              style={{
                fontFamily: "BungeeInline-Regular",
                fontSize: 65,
                textAlign: "center",
                color: "#fff",
                opacity: animated_opacity,
                transform: [{ rotateX: animated_rotation }]
              }}
            >
              {props.contents.marker}
            </Animated.Text>
          </TouchableOpacity>
        </View>
      )}
    </SocketContext.Consumer>
  );
}

export default Tile;

How Tile is used in Grid.js

import React from "react";
import { View } from "react-native";
import Tile from "../components/Tile";
import styles from "../assets/styles/main-style";

function Grid(props) {
  function onGridSelection(row, col, socket) {
    props.onGridSelection(row, col, socket);
  }

  const grid = props.grid.map((rowEl, row) => {
    const cells = rowEl.map((cellEl, col) => {
      return (
        <Tile
          row={row}
          col={col}
          contents={cellEl}
          onSelection={onGridSelection}
        />

      );
    });
    return (
      <View style={styles.grid_row} key={row}>
        {cells}
      </View>
    );
  });
  return <View style={styles.grid}>{grid}</View>;
}

export default Grid;

My goal is to have the animation run when the Tile's props.contents changes, every time a tile is pressed, and only for that tile. I'm not sure if the fact that the entire grid (props.grid) is being re-rendered every time a new tile is pressed and that's somehow creating this un-wanted feature.

however, the animation only runs for the first tile press. and it runs for every tile that's on the board ( I know this because I have an issue where previous game memory is leaking through to new game ~ a separate issue )

like image 271
Jim Avatar asked Oct 27 '22 10:10

Jim


1 Answers

Right now your useEffect is run on every render since it doesn't have any dependencies supplied as a second argument. The other issue is skipping the effect on first render.

If the initial value for the tile contents marker is null then you can solve this with something like:

  useEffect(() => {
    // exit early if the Tile contents are the initial content values
    // which means the animation shouldn't run
    if (props.contents.marker === null) {
      return;
    }
    value.setValue(0);
    Animated.timing(value, {
      toValue: 100,
      duration: 10000,
      useNativeDriver: true
    }).start();
  // note the addition of the dependency array here so this effect only runs
  // when props.contents.marker changes
  }, [props.contents.marker]);

See https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects for more info on the second argument to useEffect.

like image 173
azundo Avatar answered Oct 31 '22 08:10

azundo