Jeff S
Jeff S

Reputation: 109

Issue updating dynamically rendered elements in React app

I'm in over my head with this. Creating connect 4 game and I'm stuck at the part where I need to update the chosen cells.

How it's behaving based on this code

  1. only diagonal cells from top-left to bot-right display a red circle, but it's ignoring its container div HTML and CSS. It just takes the space of that div
  2. The other cells don't display the red circle at all.
  3. If I click anywhere after clicking on one of the cells that does produce a red circle, I get the error "Uncaught TypeError: Cannot read property 'key' of undefined"
  4. If I click on any cell after clicking on a 'non circle producing' cell, the console log adds this the end of the array

The error:

undefined: {$$typeof: Symbol(react.element), type: "div", key: "06", ref: null, props: {…}, …}
createBoards = () => {
    let gameBoard = []

    for (let i = 0; i < 6; i++) {
      gameBoard[i] = []
      for (let j = 0; j < 7; j++) {
        gameBoard[i].push(
          <div className="box" key={[i]+[j]}>
            <div
              className="black"
              id={[i]+[j]}
              onClick={ (e) => this.playerMove(e)}>
            </div>
          </div>
        )
      }
    }
    this.setState({
      gameBoard: gameBoard
    }, function () {
      console.log(this.state.gameBoard)
    })
  }
playerMove = (e) => {
    let copyUserBoard = [...this.state.gameBoard]
    let id = e.target.id
    let i = e.target.id.split('').map(Number)
    let element = copyUserBoard[i[0]][i[1]].props.children

    let clonedElement = React.cloneElement(
      element,
      { className: "red", key: id }
    )

    for (let i = 0; i < 6; i++) {
      for (let j = 0; j < 7; j++) {
        if (id === copyUserBoard[i][j].key) {
          let newArr = Object.assign([], copyUserBoard, {[[i][j]]: clonedElement})

          this.setState({
            gameBoard: newArr
          })
          console.log(this.state.gameBoard)
        }
      }
    }

  }

Upvotes: 0

Views: 90

Answers (1)

ray
ray

Reputation: 27245

I'd recommend that you maintain the state of the board independently of the DOM. Here's an example of how you might do it. Let me know if you have questions or anything needs clarification.

(I'm aware that this isn't how connect-four works. The point of this isn't the game mechanics, it's React state management.)

// how many boards to render
const NUM_BOARDS = 3;

// number of rows and columns
const BOARD_SIZE = 5;

// values for occupied cells,
// also used as css classes
const PLAYER_1 = 'p1';
const PLAYER_2 = 'p2';

// for convenience alternating players
const PLAYERS = [PLAYER_1, PLAYER_2];

// A React component for a row on a board.
// props.cells - an array of objects, each with
//  a 'value' property whose value is PLAYER_1,
//  PLAYER_2, or null
// props.onCellClick - a click handler for the 
//  to tell the board a particular cell was clicked.
const Row = ({cells, onCellClick}) => (
  <ul className="row">
{
  cells.map( ({value}, index) => (
    <li key={index}
      onClick={() => onCellClick(index)}
      className={`cell ${value == null ? 'empty' : PLAYERS[value]}`}>
        
    </li>
  ))
}
  </ul>
)

class Board extends React.Component {
  constructor (props) {
super(props);
this.state = {
  player: 0,
  rows: Array.from({length: BOARD_SIZE}, () => ({
    cells: Array.from({length: BOARD_SIZE}, () => ({ value: null }))
  }))
};
  }
  
  onCellClick = (row, cell) => {
const {rows, player} = this.state;
rows[row].cells[cell] = {value: player};
this.setState({
  rows: [...rows],
  player: (player + 1) % 2,
})
  }

  render () {
const {rows} = this.state;

return (
  <div className="board">
    <ul className="rows">
      {rows.map((row, rowIndex) => (
        <li key={rowIndex}><Row onCellClick={(cellIndex) => this.onCellClick(rowIndex, cellIndex)} cells={row.cells} /></li>
  ))}
    </ul>
  </div>
);
  }
}

class ConnectFour extends React.Component {
  render() {
return (
  <div>
    <ol>
      {
        // Emit the boards
        Array.from({length: NUM_BOARDS}).map((_, i) => (
          <li key={i}>
            <Board />
          </li>
        ))
      }
    </ol>
  </div>
)
  }
}

ReactDOM.render(<ConnectFour />, document.querySelector("#app"))
body {
  padding: 20px;
}

ul, li {
  padding: 0;
  margin: 0;
  list-style: none;
}


#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

.board {
  --size: 40px;
  padding: 10px;
  margin: 10px auto;;
}


.row {
  display: flex;
  justify-content: center;
  height: var(--size);
}

.row .cell {
  flex: 0 0 var(--size);
  margin: 1px;
}

.row > .empty {
  background: #ccc;
}

.row > .cell.p1 {
  background: red;
  border-radius: 50%
}

.row > .cell.p2 {
  background: blue;
  border-radius: 50%
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="app"></div>

Upvotes: 1

Related Questions