1

I have a Grid with 3*3 squares.

When a click on a square , we change the background color to green.

So, I tried to put the all the states in the parent GridContainer.

 state = {
    gridCells: []
  };

This will hold the indices that are clicked.

GridContainer nests Grid and Grid nests Square.

render() {
    return (
      <div>
        <Grid action={this.handler} />    
        <button>Reset Clicks</button>
      </div>
    );
  }

Here is my current implementation.

Now how do I clear the background cells when I reset clicks and make the background back to white again?

function Square(props) {
  const liClickHandler = event => {
    event.target.classList.add("highlight");
    props.clickAction();
  };
  return <li onClick={e => liClickHandler(e)} />;
}

function Grid(props) {
  const gridHandler = index => {
    props.action(index);
  };

  return (
    <ul>
      {Array.from(new Array(9)).map((item, index) => (
        <Square key={index} clickAction={() => gridHandler(index)} />
      ))}
    </ul>
  );
}

class GridContainer extends React.Component {
  state = {
    gridCells: []
  };

  handler = index => {
    let temp = [...this.state.gridCells];
    temp.push(index + 1);
    this.setState({
      gridCells: temp
    });
  };

  render() {
    return (
      <div>
        <Grid action={this.handler} />
        <button>Reset Clicks</button>
      </div>
    );
  }
}

So when I click a Sqaure , it calls a method clickAction that calls handler

that updates the state and we have an array which indices were clicked in order.

How do I implement Reset clicks that updates the background of those Sqaures back to white ? How do I let know my child know.

Am I maintaining the state wrong?

Sandbox link : https://codesandbox.io/s/3-x-3-grids-s0b43?file=/src/index.js:640-1563

StrugglingCoder
  • 4,781
  • 16
  • 69
  • 103

2 Answers2

2

I'd advise to rethink the way how your components are structured.

Each component should be independent unit with it's own logic and state (if needed of course). I'm saying if needed for state, cause ideally components should be stateless.

There are several problems with Square class:

  1. It adds class via event.target, which is not react way to go. React works with virtual DOM and has it's own set of methods to interact with html. Working with DOM directly - will bite your later, starting from writing tests for your code in the future.
  2. It does not contain incoming information whether it should be highlighted or not

Both these problems result in fact that you cannot reset presentation of your squares easily.

I've updated your sample: https://codesandbox.io/s/3-x-3-grids-uflhr?file=/src/index.js

It's still not ideal, but you can notice that gridCells is passed from top via props. And then each square gets own props param. This allows state to come through the flow and let squares rerender with updated class.

sleepwalker
  • 1,032
  • 1
  • 9
  • 15
2

In react you should think the "react" way:

  • pass the necessary state down through the props
  • pass down the callbacks so that children can update the parent state

Here is corrected version of the demo:

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";
function Square(props) {
  return (
    <li onClick={props.onClick} className={props.active ? "highlight" : ""} />
  );
}

function Grid(props) {
  let squares = [];

  for (let i = 0; i < 9; i++) {
    squares.push(
      <Square
        key={i}
        onClick={() => props.onCellClick(i)}
        active={props.cells[i]}
      />
    );
  }

  return <ul>{squares}</ul>;
}

class GridContainer extends React.Component {
  state = {
    gridCells: []
  };

  onCellClick = index => {
    this.setState(prevState => {
      const newCells = [...prevState.gridCells];
      newCells[index] = true;
      return {
        gridCells: newCells
      };
    });
  };

  render() {
    return (
      <div>
        <Grid cells={this.state.gridCells} onCellClick={this.onCellClick} />

        <button
          onClick={() => {
            let that = this; //we could bind the callback aswell
            that.setState(() => ({ gridCells: [] }));
          }}
        >
          Reset Clicks
        </button>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<GridContainer />, rootElement);
Daniel
  • 1,431
  • 2
  • 16
  • 36
  • It is okay to link to external sites, but _relevant contents_ of those links should be copied to your answer here on SO. Please include the source code of your link here in your answer. This keeps your answer useful to future readers even if codesandbox.io goes offline one day. – Mulan Apr 21 '20 at 21:06