Home > Blockchain >  Automatically trigger an OnClick event of a specific component in React
Automatically trigger an OnClick event of a specific component in React

Time:05-08

I'm a beginner to React and Javascript and I'm attempting to create Minesweeper in React from scratch. I've run into an issue where I want to replicate the functionality of Minesweeper where if you click a tile with zero mines around it, the surrounding tiles will automatically reveal themselves. However, I'm having trouble figuring out how to do that. My instinct is to grab the surrounding tiles by an id, and somehow manually trigger the OnClick event, but as far as I'm aware my Tile component has no knowledge of other Tile components and thus no way of accessing them. And I don't see how I could do it from my Board component. Any advice would be greatly appreciated!

Board Component:

class Board extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            height: 8,
            width: 8,
            num_mines: 10
        }
        game_logic.initalizeGame(8,8,10);
        game_logic.createGame();
    }

    render() {
        var all_tiles = [];
        for (var i = 0; i < this.state.height; i  ) {
            var row_tiles = [];
            for (var j = 0; j < this.state.width; j  ) {
                row_tiles.push(<Tile key={'Tile '   (i*this.state.height   j)} value={game_logic.tile_values[i][j]} />)
            }
            all_tiles[i] = row_tiles;
        }

        return (
            <div>
                {all_tiles.map((value, index) => {
                    return <div key={'Row '   index}>{value}</div>
                })} 
            </div>
        )
    }
}

export default Board;

Tile Component:

class Tile extends React.Component {

    
    constructor(props) {
        super(props);
        this.state = {
            imgSrc: null,
            imgAlt: '',
            value: '',
        };
    }

    tileClick = (e) => {
        //left click: unveil tile
        if (e.type === "click" && this.state.imgAlt === '') {
            if (this.props.value < 0) {
                this.setState({value: '', imgSrc: mine, imgAlt: "mine"});
            }
            else {
                this.setState({value: this.props.value})
                if (this.props.value === 0) {
                    //automatically left click all 8 surrounding tiles
                }
            }
        }
        //right click: mark or unmark tile
        else if (e.type === "contextmenu") {
            e.preventDefault();
            if (this.state.value === '' && this.state.imgAlt !== "mine") {
                if (this.state.imgAlt !== '') {
                    this.setState({imgSrc: null, imgAlt: ''});
                }
                else {
                    this.setState({imgSrc: flag, imgAlt: "flag"});
                }
            }
        }
    }
    
    render() {
        return (
            <button 
                className="tile" 
                onClick={this.tileClick} onContextMenu={this.tileClick}>
                    <img src={this.state.imgSrc} alt={this.state.imgAlt} />
                    {this.state.value}
            </button>
        );
    }
}

export default Tile;

CodePudding user response:

You need the child component to be able to see and do something with the parent's game_logic.tile_values. While it'd be possible to pass the values down from the parent and try to do something with them there, it'd be easier to put the click handlers in the parent so everything can be accessed from there easily.

You'll also need to change around the way you handle state. Having each individual component have its own state will make cross-component communication very difficult, but that's what you need in order for the change in one component (tile) to affect other tiles. Put the state of whether a tile's been revealed or not in the parent.

class Board extends React.Component {
    constructor(props) {
        super(props);
        game_logic.initalizeGame(8,8,10);
        game_logic.createGame();
        this.state = {
            height: 8,
            width: 8,
            num_mines: 10,
            revealed: Array.from(
                { length: 8 },
                () => new Array(8).fill(false)
            );
        }
    }
    handleReveal = (x, y, visited = new Set()) => {
        const tileKey = x   '_'   y;
        // if out of bounds, or if visited before, don't do anything
        if (visited.has(tileKey) || game_logic.tile_values[x]?.[y] === undefined) return;
        visited.add(tileKey);
        const isZero = game_logic.tile_values[x][y] === 0;
        this.setState({
            ...this.state,
            revealed: this.state.map(
                (row, i) => i !== x
                    ? row
                    : row.map(
                        (tileState, j) => y === j ? true : tileState
                    )
            )
        });
        if (isZero) {
            handleReveal(x - 1, y);
            handleReveal(x   1, y);
            handleReveal(x, y - 1);
            handleReveal(x, y   1);
        }
    };

Now that the parent component has handleReveal, pass it down to each tile. When there's a left click, call the passed down handleReveal, and have the children tiles decide what image to render by checking whether the parent's revealed state for that X and Y is true or false.

The Tiles probably shouldn't have state themselves except for whether they've been flagged or not. The imgSrc can be determined directly from the props, as can the value. Better not to duplicate state over both parents and children - doing it that way makes things messy and hard to deal with, especially when it comes to updates.

CodePudding user response:

Hello You have to update line {this.state.value} with the below {this.state.value}

  • Related