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
You can use
[...Array(n)].map()
to render an element n times, and use the index provided bymap()
to pass to the onClick:So
Can be re-written as
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 :
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:
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:
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 withoutuseMemo
if the content will never change and doesn’t rely on any local variables of the component, or put this in auseState
/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:
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 ourdata
and rendering adiv
for each item, and then within this div iterating over each sub-item and rendering aSquare
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’skey
, but this is generally bad practice and it would be better if each item had a unique ID that can be used as thekey
.