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

admin2025-04-20  0

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 ponents animate on each press, whereas I only expect an animation for the <Tile> ponent 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 .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 "../ponents/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 )

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 ponents animate on each press, whereas I only expect an animation for the <Tile> ponent 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/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 "../ponents/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 )

Share Improve this question edited Aug 5, 2019 at 20:03 Jim asked Aug 1, 2019 at 0:42 JimJim 2,3227 gold badges42 silver badges77 bronze badges 4
  • I don't fully understand the problem. Is it that all runs at the same time or just the first one? – Auticcat Commented Aug 1, 2019 at 8:00
  • both. I know that it animates all the existing <Tile> ponents because there's a bug where a previous game tile placements will bleed through on the first tile selection of the new game. then all the tiles' appearances are animated. and its only this first press, that the animation runs – Jim Commented Aug 1, 2019 at 17:24
  • try adding key to the Tile in Grid.js. – Nishant Nair Commented Aug 4, 2019 at 5:37
  • Good catch, however, it didn't help to only animate the currently pressed Tile – Jim Commented Aug 4, 2019 at 20:19
Add a ment  | 

1 Answer 1

Reset to default 3 +50

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/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects for more info on the second argument to useEffect.

转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1745125928a286424.html

最新回复(0)