Re-rendering of children on click doesn't work

Viewed 87

I am currently learning React and am trying to code a programmable grid where the boxes change color when clicked.

Unfortunately my 'Refresh Grid'-button is not working as intended. Even when clicked the boxes which got clicked before remain activated (i.e. their color is orange). As I reset the state of my grid when calling the setGrid() I am not sure where I am mistaken.

Help is much appreciated. As I am totally new to React itself some recommendations about best practices etc. would be of great help as well!

Thanks in advance.

Code:

import ReactDOM from 'react-dom';
import './index.css';

class Box extends React.Component {
  constructor() {
    super();
    this.state = {
      color: 'white',
      activated: false
    }
  }

  initiate() {
    this.setState({color: 'white', activated:false})
  }

  componentDidMount() {
    this.initiate()
  }

  click() {
    if(!this.state.activated) {
      this.setState({color: 'orange', activated: true})
    }
    else {
      this.setState({color: 'white', activated: false})
    }
  }

  render() {
    return <div className="box" style={{ backgroundColor: this.state.color }} onClick={() => this.click()}/>
  }
} 

class Grid extends React.Component {
  constructor() {
    super();
    this.state = {grid: []}
  }

  componentDidMount() {
    this.setGrid()
  }

  setGrid() {
    let grid = [];
    for (let row = 0; row < 20; row++) {
      for (let col = 0; col < 20; col++) {
        grid.push(<Box />)
      }
    }
    this.setState({grid: grid})
  }

  render() {
    return (
      <>
        <div className="grid">{this.state.grid}</div>
        <button onClick={() => this.setGrid()}>Refresh Grid</button>
      </>
      )
  }
}

ReactDOM.render(
  <Grid />,
  document.getElementById('root')
);
2 Answers

You aren't assigning a React key to the elements/Components rendered in the array, so their relative index is used, and since this doesn't change from render to render, React bails on rerendering.

  1. You can assign unique keys to each Box component when they are instantiated into the grid state. Here's an example using a GUID generator from uuid.

    import { v4 as uuidV4 } from 'uuid';
    
    ...
    
    setGrid() {
      let grid = [];
      for (let row = 0; row < 5; row++) {
        for (let col = 0; col < 5; col++) {
          grid.push(<Box key={uuidV4()} />); // <-- add key
        }
      }
      this.setState({ grid: grid });
    }
    

Edit re-rendering-of-children-on-click-doesnt-work

  1. You can use a single key on the div rendering the grid. Reset this when setting/resetting the grid.

    import { v4 as uuidV4 } from 'uuid';
    
    ...
    
    class Grid extends React.Component {
      constructor() {
        super();
        this.state = {
          grid: [],
          key: uuidV4()
        };
      }
    
      componentDidMount() {
        this.setGrid();
      }
    
      setGrid() {
        const grid = [];
        for (let row = 0; row < 5; row++) {
          for (let col = 0; col < 5; col++) {
            grid.push(<Box />);
          }
        }
        this.setState({
          grid,
          key: uuidV4() // <-- set/reset key
        });
      }
    
      render() {
        return (
          <>
            <div
              key={this.state.key} // <-- add key
              className="grid"
            >
              {this.state.grid}
            </div>
            <button onClick={() => this.setGrid()}>Refresh Grid</button>
          </>
        );
      }
    }
    

    Edit re-rendering-of-children-on-click-doesnt-work (forked)

Clear the existing grid and set the new grid

  setGrid() {
    this.setState({ grid: [] }, () => {
      let grid = [];
      for (let row = 0; row < 20; row++) {
        for (let col = 0; col < 20; col++) {
          grid.push(<Box />);
        }
      }
      this.setState({ grid: grid });
    });
  }
Related