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 )
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With