I have the component Player
. It returns a div with an image. It has the properties x
and y
.
These properties are passed as the left and top values of the div inside Player
. Outside the component Player
, I have the state playerCoordinate
. It is passed as the values for x
and y
properties of Player
.
Whenever the left arrow key is pressed, playerCoordinate
is updated. I see that playerCoordinate
is updated well when the left arrow key is pressed. However, despite the fact that playerCoordinate
is updated, it does not update the values for x
and y
.
Here are the codes:
Screen.js
import { useState, useEffect } from 'react'
import styles from './ScreenStyles'
import ScriptDisplayer from '../ScriptDisplayer/ScriptDisplayer';
import Player from '../Player/Player'
function Screen() {
const [playerCoordinate, setPlayerCoordinate] = useState({x: 641, y: 258})
function handleUserKeyPress(event) {
if (event.code === 'ArrowLeft') {
console.log(event, playerCoordinate)
setPlayerCoordinate((prev) => {
prev.x -= 1;
return prev;
})
}
}
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress)
}, [playerCoordinate])
return (
<>
<div id='screen' style={styles.screen}>
<ScriptDisplayer />
<Player x={playerCoordinate.x} y={playerCoordinate.y} />
</div>
</>
);
}
export default Screen
Player.js
import playerGif from '../../assets/player.gif'
const styles = {
playerGif: {
height: 70,
width: 70,
}
}
function Player(props) {
console.log('playerX: ', props.x, 'playerY: ', props.y)
return(
<div id='player' style={{position: 'absolute', left: `${props.x}px`, top: `${props.y}px`}}>
<img id='playerGif' src={playerGif} style={styles.playerGif} />
</div>
);
}
export default Player
I am not quite new to React and could not find any appropriate solution for the issue. What is possibly wrong here?
CodePudding user response:
use the spread operator to fire the useState changes
setPlayerCoordinate((prev) => {
prev.x -= 1;
return {...prev};
})
CodePudding user response:
- You need to return a new object for the state change to be recognised. Use the spread syntax to create a new object from the previous one, and then update the property you want.
- You don't need to add a listener each time that state changes. Use an empty dependency array to indicate you only want the listener to be added once when the component is mounted.
- And then you should add a clean up function to that effect to remove the listener when the component is unmounted.
const { useEffect, useState } = React;
function Screen() {
const [playerCoordinate, setPlayerCoordinate] = useState({x: 641, y: 258});
function handleUserKeyPress(event) {
if (event.code === 'ArrowLeft') {
setPlayerCoordinate(prev => {
return { ...prev, x: prev.x - 1 };
});
}
}
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress);
return () => {
window.removeEventListener('keydown', handleUserKeyPress);
}
}, []);
return (
<div id="screen">
<Player
x={playerCoordinate.x}
y={playerCoordinate.y}
/>
</div>
);
}
function Player(props) {
console.log('playerX: ', props.x, 'playerY: ', props.y);
return (
<div></div>
);
}
ReactDOM.render(
<Screen />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
CodePudding user response:
Hey I Just made few changes to your code to make it work on my local.
All changes have been in the screen.js files. The problem is how you update states with setPlayerCoordinate.
function Screen() {
const [playerCoordinate, setPlayerCoordinate] = useState({ x: 641, y: 258 });
useEffect(() => {
const handleUserKeyPress = (event) => {
if (event.code === "ArrowLeft") {
setPlayerCoordinate((prev) => {
return {
...prev,
x: prev.x - 1
};
});
}
};
window.addEventListener("keydown", handleUserKeyPress);
return () => window.removeEventListener("keydown", handleUserKeyPress);
}, []);
return (
<>
<div id="screen">
<Player x={playerCoordinate.x} y={playerCoordinate.y} />
</div>
</>
);
}
export default Screen;
This part is more optimised, handles desubscriptions and using the React Way