Search code examples
reactjsreact-hookstic-tac-toeuse-state

Duplicates keys warning and ui acts strange


Part of a quick learning I am trying to implement tic tac toe game and I have encountered with 2 strange problem. I tried to see the CSS and it uses Flexbox so and fixed height and width and one I fill the whole row it goes back again to original shape.

  1. I get duplicates warning keys although I give it unique keys.

  2. when click on element the square falls down! so strange.

Thanks,

import React, { useState } from 'react';
import './App.css';

const checkWinner = ({ board }) => {
}

const setXo = ({ board, setBoard, row, col, nextPlayer, setNextPlayer }) => {
  if (board[row][col] !== null)
    return alert('already in use')

  if (nextPlayer) {
    const newBoard = board
    newBoard[row][col] = 'O'
    setBoard(newBoard)
  } else {
    const newBoard = board
    newBoard[row][col] = 'X'
    setBoard(newBoard)
  }

  setNextPlayer(!nextPlayer)
}

const Row = props => {
  return (
    <button style={{ width: 100, height: 100 }} onClick={() => setXo(props)}>
      <h1>{props.board[props.row][props.col]}</h1>
    </button>
  )
}

const Column = props => props.board.map((value, index) => {
  console.log([props.row,index].join(''))
  return (
    <Row
      board={props.board}
      setBoard={props.setBoard}
      row={props.row}
      col={index}
      key={[props.row,index].join('')}
      nextPlayer={props.nextPlayer}
      setNextPlayer={props.setNextPlayer}
    />
  )
})


const Board = props => {
  console.log(props.board.length)
  return props.board.map((i, index) => {
    console.log(`index is: ${index}`)
    return (
      <div style={{ height: 100 }}>
        <Column
          key={index.toString()}
          row={index}
          style={{ marginTop: 20, border: 2, borderColor: 'black' }}
          board={props.board}
          setBoard={props.setBoard}
          nextPlayer={props.nextPlayer}
          setNextPlayer={props.setNextPlayer}
        />
      </div>)
  })
}

const App = () => {
  const INITIAL_BOARD = Array(3).fill(null).map(() => Array(3).fill(null));
  const [nextPlayer, setNextPlayer] = useState(false)
  const [board, setBoard] = useState(INITIAL_BOARD)
  const setPlayer = nextPlayer => nextPlayer ? 'Player 2' : 'Player 1'
  console.log(board)
  return (
    <div className="App">
      <h2>X/O Game</h2>
      <Board
        board={board}
        setBoard={setBoard}
        nextPlayer={nextPlayer}
        setNextPlayer={setNextPlayer}
      />
      <h2>The next player is: {setPlayer(nextPlayer)}</h2>
    </div>
  );
}
export default App;

This is the UI problem: UI


Solution

  • I'm going to answer regarding the key part.

    Your code right now is:

    const Board = props => {
      console.log(props.board.length)
      return props.board.map((i, index) => {
        console.log(`index is: ${index}`)
        return (
          <div style={{ height: 100 }}>
            <Column
              key={index.toString()}
              row={index}
              style={{ marginTop: 20, border: 2, borderColor: 'black' }}
              board={props.board}
              setBoard={props.setBoard}
              nextPlayer={props.nextPlayer}
              setNextPlayer={props.setNextPlayer}
            />
          </div>)
      })
    }
    

    As you can see, you are giving the key to the Column component. This is incorrect. The correct way to do it is to give a key to the div tag that wraps the Column.

    The reason for this is that the key is used to prevent unnecessary re-renders. If you put it in the Column, your divs are still going to be re-rendered, which is not what you want to happen. So always keep in mind - when mapping and returning a bunch of elements, always put the key on the topmost element being returned.