Browsed by
Tag: react.js

Allow / Disallow Gutenberg Toolbar Options

Allow / Disallow Gutenberg Toolbar Options

When coding a Gutenberg block plugin for WordPress and you want to remove some of the <RichText> toolbar options on that block. You can set “withoutInteractiveFormatting” and then list the allowedFormats.

<RichText
withoutInteractiveFormatting
allowedFormats={ [ 'core/bold', 'core/italic', 'core/textColor','core/strikethrough' ] }
value={ html }
/>

All Allowed Formats

  • core/bold (Bold Text)
  • core/code (Code Formating)
  • core/image (Inline Image)
  • core/italic (Italic Text)
  • core/link (Link Option)
  • core/strikethrough (Strikethrough Text)
  • core/underline (Underline Text)
  • core/textColor (Highlight)
  • core/subscript (sub characters e.g. 2)
  • core/superscript, (super characters e.g. 2nd)
  • core/keyboard, (keyboard)
  • core/unknown, (Clear Unknown Formating)
  • core/language, (Language Options)
Learning React.js

Learning React.js

I’ve decided to learn how to use react.js in order to create my XD plugin. I felt the best way to start was probably to go through some of React’s basic tutorials and write down my notes as I go along.

The two tutorials are located here :

Create a New React App
React.js Main Tutorial – Tic Tac Toe

I already have node.js and npm installed see Learning Node.js and Webpack

$ npx create-react-app my-app
$ cd my-app
$ npm start

After follow these steps you end up with the following screen:

Screenshot of the local working app.

JSX Note
“Most React developers use a special syntax called “JSX” which makes the react element easier to write.”

https://reactjs.org/tutorial/tutorial.html

Chrome and Firefox have addons for debugging and inspecting React.js componants.

“Immutability makes complex features much easier to implement. Later in this tutorial, we will implement a “time travel” feature that allows us to review the tic-tac-toe game’s history and “jump back” to previous moves. This functionality isn’t specific to games — an ability to undo and redo certain actions is a common requirement in applications. Avoiding direct data mutation lets us keep previous versions of the game’s history intact, and reuse them later.”

“Detecting changes in immutable objects is considerably easier. If the immutable object that is being referenced is different than the previous one, then the object has changed.”

https://reactjs.org/tutorial/tutorial.html

this.state : refers to the current component
this.props : refers to the parent component

Taking it Further

The React tutorial has a few suggestions for taking the tic tac toe game example further. I’m going to have a go at them and see if I can complete them.

Display the location for each move in the format (col, row) in the move history list.

I’m going to modify the history constant to also record the location of the move (so I don’t have to do things like comparing arrays which is probably more complicated).

class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            history: [{
                squares: Array(9).fill(null),
                location: null,
            }],
            stepNumber: 0,
            xIsNext: true,
        };
    }
    // Rest is the same...
}

I’ve added in another part “location: null,”

I plan to store the cell key here.

handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
        
    if(calculateWinner(squares) || squares[i]) {
        return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
        history: history.concat( [{
            squares: squares,
            location: i
        }]),
        stepNumber: history.length,
        xIsNext: !this.state.xIsNext,
    });
}

Then in the handleClick(i) I modified the setState to record the location.

Then I change the render() function in the Game class to the following:

render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);
        
    const moves = history.map((step, move) => {
        const desc = move ?
            'Go to move #' + move + ' Location: #' + history[move].location :
            'Go to game start';
        return (
            <li key={move}>
                <button onClick={() => this.jumpTo(move)}>{desc}</button>
            </li>
        );
    });

    // rest is the same....
}

So it is now displaying the location. However the challenge was to show the column and the row so I need to translate that number into the column and row.

I ended up creating an array of all the possible locations in handleClick()

handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    const locations = [
        [1,1],
        [2,1],
        [3,1],
        [1,2],
        [2,2],
        [3,2],
        [1,3],
        [2,3],
        [3,3]
    ];
    // ....
}

Then instead of just recording the square key in the location parameter I changed it to the value of the locations array.

handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    const locations = [
        [1,1],
        [2,1],
        [3,1],
        [1,2],
        [2,2],
        [3,2],
        [1,3],
        [2,3],
        [3,3]
    ];
        
    if(calculateWinner(squares) || squares[i]) {
        return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
        history: history.concat( [{
            squares: squares,
            location: locations[i]
        }]),
        stepNumber: history.length,
        xIsNext: !this.state.xIsNext,
    });
        
}

Task one complete.

Bold the currently selected item in the move list.

Easiest way to do this is to put bold tags around the button. The stepNumber keeps track of what move it currently is.

render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);
        
    const moves = history.map((step, move) => {
        const desc = move ?
            'Go to move #' + move + ' Column: ' + history[move].location[0] + ' Row: ' + history[move].location[1] :
            'Go to game start';
        return (
            <li key={move}>
                <button onClick={() => this.jumpTo(move)}>
                    {move == this.state.stepNumber ? <strong>{desc}</strong> : desc}
                </button>
            </li>
        );
    });
}

This checks if the current move number matches the overall step number. If it does it adds some bold tags.

Things I noticed….

You don’t need to enclose the html in quotes
The variable being referenced has to be in one of the parent elements (which makes sense) and it has to be passed from the parent element to the child element.
For some reason the last desc doesn’t need brackets – I’m not sure why this is the case at present. I’m assuming it’s because there’s no html so the brackets just escape the variable from the html string? If there was html I suspect it would need brackets.

Rewrite Board to use two loops to make the squares instead of hardcoding them.

I found this a bit tricker to solve but my solution ended up as follows:

Added a variable to the state of the Game component

class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            history: [{
                squares: Array(9).fill(null),
                location: null,
            }],
            stepNumber: 0,
            xIsNext: true,
            cols: 3,
            rows: 3,
        };
    }

    // ....
}

Then modified the Game render() of Board to include the two new variables:

        return (
            <div className="game">
                <div className="game-board">
                    <Board 
                        squares={current.squares}
                        cols={cols}
                        rows={rows}
                        onClick={(i) => this.handleClick(i)}
                    />
                </div>
                <div className="game-info">
                    <div>{status}</div>
                    <ol>{moves}</ol>
                </div>
            </div>
        );

Modified renderSquare to have keys (otherwise you will get warning errors)

    renderSquare(i) {
        return (
            <Square 
                key={i}
                value={this.props.squares[i]}
                onClick={() => this.props.onClick(i)} 
            />
        );
    }

Added a renderRow function to create each individual row

    renderRow(currentRow) {
        let row = [];
        const count = currentRow * this.props.cols;
        
        for(let i=0; i < this.props.cols; i++) {
            const key = count + i;
            row.push(this.renderSquare(key));
        }
        
        return (
            <div key={currentRow} className="board-row" id={currentRow}>
                {row}
            </div>);
    }

This should recieve the current row numer (e.g. 0, 1 or 2 for each of the 3 rows).

The counter will always start at the current row number times the total number of columbs. e.g.

If the number of columns is 3

0 * 3 = 0 // Start on Cell Number 0
1 * 3 = 3 // Start on Cell Number 3
2 * 3 = 6 // Start on Cell Number 6

A for loop is then run to create the number of columns specified.
Each key is then calculated by the current Cell Number + the current column number.
e.g

Row 0:

Cell Number Start 0 + Column 0 = Key 0
Cell Number Start 0 + Column 1 = Key 1
Cell Number Start 0 + Column 2 = Key 2

Row 1:

Cell Number Start 3 + Column 0 = Key 3
Cell Number Start 3 + Column 1 = Key 4
Cell Number Start 3 + Column 2 = Key 5

Row 2:

Cell Number Start 6 + Column 0 = Key 6
Cell Number Start 6 + Column 1 = Key 7
Cell Number Start 6 + Column 2 = Key 8

Each column then adds a square with the relevant key to an array.
Finally the finished row is returned by the function.

Added a function called renderRows to the Board component:

   renderRows() {
        let gameBoard = [];
        
        for(let i=0; i < this.props.rows; i++) {
            gameBoard.push(this.renderRow(i));
        }
        return gameBoard;
    }

For each row the renderRow function is run and adds the result to the gameBoard array which is then returned by the function.

Finally I edited the render function to the below:

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

This has the added bonus of meaning you can expand the board size. However for that to work I need to do a few extra things!

First I need to add a variable for locations to the main Game component.

class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            history: [{
                squares: Array(9).fill(null),
                location: null,
            }],
            stepNumber: 0,
            xIsNext: true,
            cols: 3,
            rows: 3,
            locations: [],
        };
    }
    // ...
}

In the Game.render() I also need to add the variable to the Board Component so the Board class can manipulate it.

<Board 
    squares={current.squares}
    cols={cols}
    rows={rows}
    locations={this.state.locations}
    onClick={(i) => this.handleClick(i)}
/>

Then I modify Board.renderRow() to update the locations variable as it creates the board.

    renderRow(currentRow) {
        let row = [];
        const count = currentRow * this.props.cols;
        
        for(let i=0; i < this.props.cols; i++) {
            const key = count + i;
            
            let location = [(currentRow + 1),(i + 1)];
            this.props.locations.push(location);

            row.push(this.renderSquare(key));
        }
        
        return (
            <div key={currentRow} className="board-row" id={currentRow}>
                {row}
            </div>);
    }

Then I updated the Game.handleClick() to use the state variable locations. And also to pass along the number of rows and columns to

    handleClick(i) {
        const history = this.state.history.slice(0, this.state.stepNumber + 1);
        const current = history[history.length - 1];
        const squares = current.squares.slice();
        const locations = this.state.locations;
        const rows = this.state.rows;
        const cols = this.state.cols;

        if(calculateWinner(squares, rows, cols) || squares[i]) {
            return;
        }

        // ....
   }

And Finally I updated the CalculateWinner function…

function calculateWinner(squares, rows, cols) {
    
    const wLines = [];
    
    for(let j=0; j < rows; j++) {
        
        let currentRow = j;
        let startCell = currentRow * cols;
        
        for(let k=0; k < cols; k++) {
            
            let key = startCell + k;
            
            // horizontal rows e.g. 0 1 2
            if(key + 2 <= (startCell + (cols - 1))) {
                let winner = [key,(key+1),(key+2)];
                wLines.push(winner);
            }
            
            // vertical columns e.g. 0 3 6
            if((currentRow + 2) <= (rows - 1) ) {
                let winner = [key,(key+cols),(key+(cols * 2))];
                wLines.push(winner);
            }
            
            if((currentRow + 2) <= (rows - 1) ) {
                // diagonals top left to bottom right e.g. 0 4 8
                if(key + 2 <= (startCell + (cols - 1))) {
                    let winner = [key,(key+cols+1), (key+(cols * 2)+2)];
                    wLines.push(winner);
                }
                // diagonals top right to bottom left e.g. 2 4 6
                if( key - 2 >= startCell ) {
                    let winner = [key, (key+cols-1),(key+(cols * 2)-2)];
                    wLines.push(winner);
                }
            }
        }
    }
    
    for( let i=0; i< wLines.length; i++) {
        const [a,b,c] = wLines[i];
        if(squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
            return squares[a];
        }
    }
    return null;
}

Add a toggle button that lets you sort the moves in either ascending or descending order.

This one seemed a bit easier. First I added the toggle button to the Game Info, in Game ->render().

<div className="game-info">
    <div>{status}</div>
    <div className="order-moves">
        <button onClick={() => this.reOrderMoves()}>
            Toggle Order {currentOrder}
        </button>
    </div>
    <ol>{moves}</ol>
</div>

Then I added a variable to the state to record the order.

class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            history: [{
                squares: Array(9).fill(null),
                location: null,
            }],
            stepNumber: 0,
            xIsNext: true,
            cols: 10,
            rows: 10,
            locations: [], 
            sortOrderDesc: false
        };
    }

    //...
}

Then I added a handler for the button that changes the state to the opposite of the current state.

    reOrderMoves() {
        this.setState({
            sortOrderDesc: (this.state.sortOrderDesc === true ? false : true),
        })
        this.render();
    }

Finally I made some changes to the Game->render() function.

    render() {
        
        // ....

        const sortOrderDesc = this.state.sortOrderDesc;
        const currentOrder = (sortOrderDesc ? '(Ascending)' : '(Descending)');
        
        
        const moves = history.map(...);
        
        if(sortOrderDesc === true) {
            moves.reverse();
        }

        // ....
}

This reverses the order of the moves array so it renders in reverse.

When someone wins, highlight the three squares that caused the win.

Ok I decided I need some way of identifying each square so I modified the Square component so that I can target the correct square.

function Square(props) {
    return (
        <button id={props.id} className={props.className} onClick={props.onClick}>
            {props.value}
        </button>
    )
}

Then I edited the Render Square function so the code sets the id and the className. I’m not sure which I’m going to use but this will give me a couple to try.

    renderSquare(i) {

        let className = 'square';
        
        if(this.props.winner) {
            let wRow = this.props.winner[1];
            for(let j = 0; j<wRow.length; j++) {
                if(wRow[j] == i) {
                    className = 'square winner';
                }
            }
        }
        return (
            <Square 
                key={i}
                id={i}
                className={className}
                value={this.props.squares[i]}
                onClick={() => this.props.onClick(i)}
            />
        );
    }

(Later I settled on changing the class name if the number was in the winning row).

Then I edited the CalculateWinner function so it returns the winning row as well as the winner.

function calculateWinner(squares, rows, cols) {

    // ...

    for( let i=0; i< wLines.length; i++) {
        const [a,b,c] = wLines[i];
        if(squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
            return [squares[a], wLines[i]];
        }
    }
    return null;
}

Then I modified everywhere that Calculate Winner was referenced so it could still handle it properly.

    render() {
         // ....
         const winner = calculateWinner(current.squares, rows, cols);

         // ....

         if(winner) {
            status = 'Winner: ' + winner[0];
            
        } else {
            status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
        }

        // ....

    }

Finally I added some CSS

.square.winner {
    background: green;
    color: #fff;
}

When no one wins, display a message about the result being a draw.

Last one 🙂

My solution was to check whether any of the squares were still null. If they weren’t and no winner had been selected it returned the draw result.

First a small modification to renderSquare to check the second array is not null.

renderSquare(i) {
        
        let className = 'square';
        
        if(this.props.winner && this.props.winner[1] !== null) {
            let wRow = this.props.winner[1];
            for(let j = 0; j<wRow.length; j++) {
                if(wRow[j] === i) {
                    className = 'square winner';
                }
            }
        }
        // ....
}

Then I modified calculateWinner function to include a bit below the winning line check.

function calculateWinner(squares, rows, cols) {
    
    //....

    let numberNotNull = 0;
    
    for( let j=0; j< squares.length; j++) {
        if(squares[j] !== null) {
            numberNotNull++;
        }
    }
    
    if(numberNotNull === (rows * cols)) {
        return ['Draw',null];
    }
    
    
    return null;
}

Having finished my game I ran the build function to create an optimised version.

$ yarn build
The Finished React Project