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 theTile
inGrid.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
1 Answer
Reset to default 3 +50Right 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
.