skip to Main Content

I was learning the basics of ReactJS by creating a tic-tac-toe game and the board was initialy developed as bellow:

<div className="board-row">
    <Square value={squares[0]} onSquareClick={() => handleSquareClick(0)} />
    <Square value={squares[1]} onSquareClick={() => handleSquareClick(1)} />
    <Square value={squares[2]} onSquareClick={() => handleSquareClick(2)} />
</div>

… and so on another 2 times.

Here’s the Square component and its Props:

interface SquareProps {
  value: string;
  onSquareClick: () => void;
}
function Square({ value, onSquareClick }: SquareProps) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

When I click on each Square component, an onClick function is triggered passing the respective "id" of the clicked Square and then it is filled with the value Prop, that can be an "X" or an "O", depending on the respective index of the squares array passed.

Now I was trying to do this without all that hardcode, just using for loops.

I wrote the following function, but the Square Props value and onSquareClick didn’t work on each Square component as they should like the previous code above.

const generateBoard = () => {
    const colsArr = Array(3);
    const cellsArr = Array(3);
    let cellId = 0;

    for (let i = 0; i < cellsArr.length; i++) {
      for (let j = 0; j < colsArr.length; j++) {
        colsArr[j] = (
          <Square
            key={j}
            value={squares[cellId]}
            onSquareClick={() => handleSquareClick(cellId)}
          />
        );
        cellsArr[i] = (
          <div key={i} className="board-row">
            {colsArr}
          </div>
        );
        cellId++;
      }
    }

    return cellsArr;
};

2

Answers


  1. You can use [...Array(n)].map() to render an element n times, and use the index provided by map() to pass to the onClick:


    So

    <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleSquareClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleSquareClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleSquareClick(2)} />
    </div>
    

    Can be re-written as

    <div className="board-row">
        {
            [...Array(n)].map((e, i) => (
                <Square value={squares[i]} onSquareClick={() => handleSquareClick(i)} />
            ))
        }
    </div>
    

    Regarding the 3 rows, no need to make you DOM more complicated, I’d use a Flexbox to wrap it after 3 elements:


    Small React Demo to show this :

    function Square({ value, onSquareClick }) {
      return (
        <button className="square" onClick={onSquareClick}>
          {value}
        </button>
      );
    }
    
    const Example = () => {
    
        const [squares, setSquares] = React.useState(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ]);
        
        const handleSquareClick = (n) => console.info('Pressed button', n);
    
        return (
            <div>
                <h1>{'Example'}</h1>
                <div className="board">
                    {
                        [...Array(9)].map((e, i) => (
                            <Square value={squares[i]} onSquareClick={() => handleSquareClick(i)} />
                        ))
                    }
                </div>
            </div>
        )
    }
    ReactDOM.render(<Example />, document.getElementById("react"));
    .board {
        width: 150px;
        
        display: flex;
        flex-wrap: wrap;
    }
    
    .board > .square {
        flex-basis: 33%;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>
    Login or Signup to reply.
  2. You appear to be mixing data and views. Instead your views should be driven by your data. Additionally, I would avoid using for-loops altogether in React and instead rely on Array.map, and use hooks to prevent data being unnecessarily re-initialized.

    Let’s start by defining our data:

    const data = useMemo(() => {
      return [...Array(3)].map((_, index) => [...Array(3)])
    }, []);
    

    Here we are creating an array with 3 items. Note that by spreading (...) the array into an array we have made it iterable (so we can call .map and other iteration methods on it).

    Then we’re using .map to iterate over this array and create another array of 3 items within each of the outer array items.

    This results in something like:

    [
      [undefined, undefined, undefined],
      [undefined, undefined, undefined],
      [undefined, undefined, undefined]
    ]
    

    I’ve wrapped this initialization within a useMemo hook so that it won’t be re-run, but you could also define this outside of your component without useMemo if the content will never change and doesn’t rely on any local variables of the component, or put this in a useState/useReducer if you may later update the data.

    Now we have some data we can iterate over it and create some components. This can be done straight inside the returned JSX of our component:

    return (
      <>
        {data.map((_, i) => (
          <div key={i} className="board-row">
            {i.map((_, j) => (
              <Square key={j} value={j} onClick={() => handleSquareClick(j)} />
            ))}
          </div>
        ))}
      </>
    );
    

    Here we are first returning a React fragment <></> as you cannot return plain arrays in React components. Then within the fragment we’re first iterating over our data and rendering a div for each item, and then within this div iterating over each sub-item and rendering a Square with values matching the items we’re iterating over.

    Note that at both levels we are applying a key so that React can track the items. At the moment we’re just using the index for each item’s key, but this is generally bad practice and it would be better if each item had a unique ID that can be used as the key.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search