Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Same result on logging the state after render

I understand that useState is asynchronous. Please read the full question before answering.

I'm trying to modify an element in an array with useState but it doesn't work as expected.

The array and the function to modify it:

const [table, setTable] = useState(['blue', 'blue', 'blue', 'blue', 'blue']);

let currentShapePosition = 2;

function printTable() {
  let newTable = [...table];
  // let newTable = table;
  newTable[currentShapePosition] = 'red';
  setTable(newTable);
  console.log('printTable newTable', newTable); // <-- the result is as expected
  // log => printTable newTable ["blue", "blue", "red", "blue", "blue"]
  console.log('printTable table', table); // <--- The problem is here. I don't get why the array never update
  // log => printTable newTable ["blue", "blue", "blue", "blue", "blue"]
}

Because useState is asynchronous, I understand that the array may not change immediately, but, inside the printTable function the console.log result is the same even after several re-renders.

When instead of: let newTable = [...table]

I do this : let newTable = table

Then the state is updated inside the console.log generated in the function but then there is no re-rendering/component update.

I would like to understand why in the first case newTable = [...table] inside the function the console.log result is the same after several re-renders. And why in the second case newTable = table there is no re-rendering of the component despite setTable(newTable).

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./style.css";

const App = () => {
  let currentShapePosition = 2;

  const [table, setTable] = useState(["blue", "blue", "blue", "blue", "blue"]);

  useEffect(() => {
    printTable();
    window.addEventListener("keydown", handleKeyPress);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    console.log("render", table);
  });

  function printTable() {
    let newTable = [...table];
    // let newTable = table;
    newTable[currentShapePosition] = "red";
    setTable(newTable);
    console.log("printTable newTable", newTable); // <-- the result is as expected
    console.log("printTable table", table); // <--- The problem is here. I don't get why the initial value never change
  }

  function handleKeyPress(event) {
    switch (event.key) {
      case "Left": // IE/Edge specific value
      case "ArrowLeft":
        moveShape(-1);
        break;
      case "Right": // IE/Edge specific value
      case "ArrowRight":
        moveShape(1);
        break;
      default:
        return;
    }
  }

  function moveShape(direction) {
    currentShapePosition += direction;
    printTable();
  }

  return (
    <table className="tetris-table">
      <tbody>
        <tr>
          <td className={table[0]} />
          <td className={table[1]} />
          <td className={table[2]} />
          <td className={table[3]} />
          <td className={table[4]} />
        </tr>
      </tbody>
    </table>
  );
};

const root = document.getElementById("root");

ReactDOM.render(<App />, root);

Edit fervent-field-787ky

like image 890
Nico Avatar asked Mar 20 '26 07:03

Nico


1 Answers

The problem is your first useEffect, because you removed the eslint warning, it hidden a potential bug.

useEffect(() => {
  printTable();
  window.addEventListener('keydown', handleKeyPress);
  //   v hidden bug, you should consider the warnings
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

What happened here, is that on component mount the first instance of handleKeyPress assigned to addEventListener (refer to closures), where all array was of value "blue" and it will keep it that way until the unmount.

You should be aware that on every render the component body executed, therefore in your case, there is a new instance of every function.

The same goes for currentPosition that should be a reference.

To fix it, remove the eslint comment and go with the warnings:

useEffect(() => {
  const printTable = () => {
    let newTable = [...table];
    newTable[currentPosition.current] = 'red';
    setTable(newTable);
    console.log('closure', table);
  };

  function handleKeyPress(event) {
    switch (event.key) {
      case 'Left': // IE/Edge specific value
      case 'ArrowLeft':
        moveShape(-1);
        break;
      case 'Right': // IE/Edge specific value
      case 'ArrowRight':
        moveShape(1);
        break;
      default:
        return;
    }
  }

  function moveShape(direction) {
    currentPosition.current += direction;
    printTable();
  }

  window.addEventListener('keydown', handleKeyPress);

  return () => window.removeEventListener('keydown', handleKeyPress);
}, [table]);

Edit Q-58329134-LogTable

like image 166
Dennis Vash Avatar answered Mar 21 '26 19:03

Dennis Vash