Home > Enterprise >  properties that have the values of a state is not changed when the state is updated
properties that have the values of a state is not changed when the state is updated

Time:12-22

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:

  1. 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.
  2. 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.
  3. 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

  • Related