Search code examples
reactjsreact-hooksreact-statereact-state-management

React state does not change after click


I am learning react. What my code should do: It should render the squares based on their stars value in corresponding line. Lines have numbers through 1 to 5, and squares have stars trhough 1 to 5. If the square has stars value of 3, then it should render in line with the same number. If you change the stars value, square should render in different line.

Below is my code.

import react from "react";
import { useState } from "react";

const squares = [
    {'id': 1, 'stars': '5'},
    {'id': 2, 'stars': '4'},
    {'id': 3, 'stars': '3'},
    {'id': 4, 'stars': '3'},
    {'id': 5, 'stars': '5'},
    {'id': 6, 'stars': '4'},
    {'id': 7, 'stars': '2'},
    {'id': 8, 'stars': '1'}
]

const lines = [
    {'id': 1, 'numberName':'One','number': 1},
    {'id': 2, 'numberName':'One','number': 2},
    {'id': 3, 'numberName':'One','number': 3},
    {'id': 4, 'numberName':'One','number': 4},
    {'id': 5, 'numberName':'One','number': 5}
]


function Lines() {
    return (
        lines.map(line => <Line {...line} key={line.id}/>)
    )
}


function Line(props) {
    const [squaresArray, setSquaresArray] = useState(squares)
    function changeSquares(squareId, valueForChange) {
        let oldSquares = squaresArray
        let squareToChange = oldSquares.find(square => square.id == squareId)
        squareToChange.stars = valueForChange
        setSquaresArray(oldSquares)
        console.log(squareId, valueForChange)
    }

    return(
        <div style={{'display':'flex', 'border':'3px red solid', 'padding': '10px'}}>
            {
                squaresArray
                    .filter(square => square.stars == props.number)
                    .map(square => 
                    <Square {...square} changeSquares={changeSquares} squareId={square.id} key={square.id}/>
                )
            }
        </div>  
    )
}

function Square(props) {
    const [stars, setStars] = useState(props.stars)
    return (
        <div style={{'width': '100px','height': '100px','border': 'solid 2px blue','margin-right': '5px'}}>
                <input type='number' name='numb' value={stars} style={{'width':'25px'}} onChange={(e) => {setStars(e.target.value)}}></input>
                <button onClick={() => {props.changeSquares(props.squareId,stars)}} >Save</button>
        </div>
    )
}

function App() {  
    return (
        <Lines lines={lines}/>
    )
}

export default App;

There is onClick function on line 61. This function should change the state of page and rerender my app based on new square values. But only thing that happens is the console.log on line 40. And I can see that on line 39 variable oldSquares is passed correctly with updated star value after clicking button, but the squares are NOT rerendering.

Please help.


Solution

  • The state is mutated in the changeSquares handler and the array reference is never updated.

    function changeSquares(squareId, valueForChange) {
      let oldSquares = squaresArray // <-- reference to state
      let squareToChange = oldSquares.find(square => square.id == squareId)
      squareToChange.stars = valueForChange // <-- mutation!!
      setSquaresArray(oldSquares) // <-- same reference saved back into state
      console.log(squareId, valueForChange)
    }
    

    Use a functional state update to update from the previous state and create a shallow copy of the array into a new array reference.

    function changeSquares(squareId, valueForChange) {
      setSquaresArray(squares => squares.map(square => square.id == squareId
        ? {
          ...square,
          stars: valueForChange
        }
        : square
      ));
      console.log(squareId, valueForChange)
    }