Search code examples
javascriptreactjsfunctionjsxrender

React component not carrying over functions from class when re-rendering


I'm attempting to build a Minesweeper game as practice using React. I have a main Minesweeper.js file with all of the functionality.

export class Tile {
  constructor(board, pos) {
      this.board = board;
      this.pos = pos;
      this.bombed = false;
      this.explored = false;
      this.flagged = false;
  }

  adjacentBombCount() {
    let bombCount = 0;
    this.neighbors().forEach(neighbor => {
      if (neighbor.bombed) {
        bombCount++;
      }
    });
    return bombCount;
  }

  explore() {
    if (this.flagged || this.explored) {
      return this;
    }

    this.explored = true;
    if (!this.bombed && this.adjacentBombCount() === 0) {
      this.neighbors().forEach(tile => {
        tile.explore();
      });
    }

  }

  neighbors() {
    const adjacentCoords = [];
    Tile.DELTAS.forEach(delta => {
      const newPos = [delta[0] + this.pos[0], delta[1] + this.pos[1]];
      if (this.board.onBoard(newPos)) {
        adjacentCoords.push(newPos);
      }
    });

    return adjacentCoords.map(coord => this.board.grid[coord[0]][coord[1]]);
  }

  plantBomb() {
    this.bombed = true;
  }

  toggleFlag() {
    if (!this.explored) {
      this.flagged = !this.flagged;
      return true;
    }

    return false;
  }
}

Tile.DELTAS = [[-1, -1], [-1,  0], [-1,  1], [ 0, -1],
             [ 0,  1], [ 1, -1], [ 1,  0], [ 1,  1]];

export class Board {
  constructor(gridSize, numBombs) {
    this.gridSize = gridSize;
    this.grid = [];
    this.numBombs = numBombs;
    this.generateBoard();
    this.plantBombs();
  }

  generateBoard() {
    for (let i = 0; i < this.gridSize; i++) {
      this.grid.push([]);
      for (let j = 0; j < this.gridSize; j++) {
        const tile = new Tile(this, [i, j]);
        this.grid[i].push(tile);
      }
    }
  }

  onBoard(pos) {
    return (
      pos[0] >= 0 && pos[0] < this.gridSize &&
        pos[1] >= 0 && pos[1] < this.gridSize
    );
  }

  plantBombs() {
    let totalPlantedBombs = 0;
    while (totalPlantedBombs < this.numBombs) {
      const row = Math.floor(Math.random() * (this.gridSize - 1));
      const col = Math.floor(Math.random() * (this.gridSize - 1));

      let tile = this.grid[row][col];
      if (!tile.bombed) {
        tile.plantBomb();
        totalPlantedBombs++;
      }
    }
  }

  lost() {
    let lost = false;
    this.grid.forEach(row => {
      row.forEach(tile => {
        if (tile.bombed && tile.explored) {
          lost = true;
        }
      });
    });
    return lost;
  }

  won() {
    let won = true;
    this.grid.forEach(row => {
      row.forEach(tile => {
        if (tile.flagged === tile.revealed || tile.flagged !== tile.bombed) {
          won = false;
        }
      });
    });
    return won;
  }
}

Then I have three React components: Board.jsx

import React from "react";
import Tile from "./tile";

class Board extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return(
            <div>
                {this.props.board.grid.map((row, idx) => {
                    return (<div key={idx}>
                       {row.map((tile, idx) => {
                        return (
                            <div key={idx}>
                                <Tile
                                tile={tile}
                                updateGame={this.props.updateGame}/>
                            </div>
                        )
                       })} 
                    </div>)
                })}
            </div>
        )
    }
}

export default Board;

Tile.jsx

import React from "react";

class Tile extends React.Component {
    constructor(props) {
        super(props)
        this.checkState = this.checkState.bind(this)
    }

    checkState() {
        console.log(this)
        if (this.props.tile.bombed) {
            return "💣"
        } else if (this.props.tile.explored) {
            return (this.props.tile.adjacentBombCount())
        } else if (this.props.tile.flagged) {
            return "⚐"
        } else {
            return ""
        }
    }

    render() {
        return(
            <div>{this.checkState()}</div>
        )
    }
}

export default Tile;

and finally a Game.jsx

import React from "react";
import * as Minesweeper from "../minesweeper"
import Board from "./board";

class Game extends React.Component {
    constructor(props) {
        super(props);
        let board = new Minesweeper.Board(10, 5)
        this.state = {
            board: board
        }
    this.updateGame = this.updateGame.bind(this);
    }

    updateGame() {

    }

    render() {
        return(
            <Board 
            board={this.state.board}
            updateGame={this.updateGame}/>
        )
    }
}

export default Game

Within Tile.jsx, I want the render to display adjacent bomb counts using the function from the main Tile js class (in minesweeper.js). This function is accessible when I first render my Tiles as React Components; however, if I change something (by going into components and updating), the function is no longer accessible. To help explain, here is the console.log of this.props.tile on the first render:

adjacentBombCount function accessible on first render

However, after re-rendering from updating a component, this is what this.props.tile looks like re-render, function missing.

Therefore, it will cause an error when I try to add the bomb count, as it cannot find the function. Can someone explain why the function is disappearing and how I can access it when Components change? Thanks!


Solution

  • This was an issue with the React DevTools specifically, and it will be addressed in an update, see: https://github.com/facebook/react/issues/24781