React – Using ternary to apply CSS class in functional component

  Kiến thức lập trình

I’m relatively new to React and working on a John Conway – Game of Life app. I have built a Gameboard.js functional component for the board itself (which is a child of App.js) and a Square.js functional component which represents an individual square in the board (and is a child of Gameboard and a grandchild of App).

In App I have a function called alive which I want to change the color of an individual square when it is clicked by the user. App also has an ‘alive’ property in it’s state set to false initially and alive will change the property to true when called.

Here is App.js:

import React, { Component } from 'react';
import './App.css';
import GameBoard from './GameBoard.js';
import Controls from './Controls.js';

    class App extends Component {
      constructor(props){
        super(props);

        this.state = {
          boardHeight: 50,
          boardWidth: 30,
          iterations: 10,
          reset: false,
          alive: false
        };
      }

      selectBoardSize = (width, height) => {
        this.setState({
          boardHeight: height,
          boardWidth: width
        });
      }

      onReset = () => {

      }

      alive = () => {
        this.setState({ alive: !this.state.alive });
        console.log('Alive function has been called');

      }



      render() {
        return (
          <div className="container">
            <h1>Conway's Game of Life</h1>

          <GameBoard
            height={this.state.boardHeight}
            width={this.state.boardWidth}
            alive={this.alive}
          />

            <Controls
              selectBoardSize={this.selectBoardSize}
              iterations={this.state.iterations}
              onReset={this.onReset}
            />

          </div>
        );
      }
    }

    export default App;

Gameboard looks like this and passes props.alive to Square:

import React, { Component } from 'react';
import Square from './Square.js';

const GameBoard = (props) => {
    return (
      <div>
        <table className="game-board">
          <tbody>
            {Array(props.height).fill(1).map((el, i) => {
              return (
                <tr key={i}>
                  {Array(props.width).fill(1).map((el, j) => {
                    return (
                      <Square key={j} alive={props.alive}/>
                    );
                  })}
                </tr>
              );
            })}
          </tbody>
         </table>
      </div>
    );
}

export default GameBoard;

In my CSS I have a class called active that changes the color of an individual square if it is clicked on. How can I make it so that in Square if a td element is clicked the color changes (i.e. the CSS classes is changed to active)?

I’ve tried this:

import React, { Component } from 'react';

const Square = (props) => {

  return(
    <td className={props.alive ? "active" : "inactive"} onClick={() => props.alive()}></td>
  );
}

export default Square;

The CSS looks like this:

.Square {
  background-color: #013243; //#24252a;
  height: 12px;
  width: 12px;
  border: .1px solid rgba(236, 236, 236, .5);
  overflow: none;

  &:hover {
    background-color: #48dbfb; //#00e640; //#2ecc71; //#39FF14;
  }
}

.inactive {
  background-color: #013243; //#24252a;
}

.active {
  background-color:  #48dbfb;
}

How can I make it so the .Square CSS class is ALWAYS applied to every square but the individual square color is changed if it’s active? In other words, can I set Square‘s td to always be styled with the .Square CSS class and then individual elements within Square can be colored appropriately depending on whether or not alive is true in App‘s state?

Is there are ternary approach to always set one particular CSS class and then, in addition, set 1 of 2 other classes….i.e. the Square CSS class is always shown and active or inactive is rendered depending on logic/state?

3

The comments have the right idea.

You could use a template literal and embed ternary conditionals in that:

return (
    <td
      className={`Square ${props.alive ? "active" : "inactive"}`}
      onClick={() => props.alive()}
    ></td>
);

A quick refesher on template literals: use backticks to wrap a string, and you can insert a JavaScript expression inside of that by wrapping it in the ${} pattern. As a bonus, template literals can span multiple lines, so no more awkward string concatenation!

const myName = "Abraham Lincoln";
const myString = `Some text.
  This text is on the next line but still in the literal.
  Newlines are just fine.
  Hello, my name is ${myName}.`;

Edit: The bigger problem that I see now is that you’re not storing the state of each your cells anywhere. You have only a single boolean stored in App called alive… what you really need is an array of booleans, with each boolean representing the state of a single Square.

The array of “alive” states should live in the App or GameBoard, following the React principle of “the data flows down”. In your case you could try keeping it in App, and that way GameBoard and Square can remain purely functional components.

Inside of App you could create a new 2-dimensional array, board, in the constructor and fill it with sub-arrays of 0 values initially:

// App.js
constructor(props){
    super(props);

    this.state = {
      boardHeight: 50,
      boardWidth: 30,
      board: [],
      iterations: 10,
      reset: false,
    };

    this.state.board = new Array(this.state.boardHeight).fill(new Array(this.state.boardWidth).fill(0));
  }

In the board array, each index represents one row. So a simplified example of [[0, 0, 1], [0, 1, 0], [1, 1, 1]] would represent:

0 0 1
0 1 0
1 1 1

GameBoard should render your grid of cells based purely on the board prop passed to it, and pass each Square its alive value and callback function as props:

const GameBoard = (props) => {
    return (
      <div>
        <table className="game-board">
          <tbody>
            {this.props.board.map((row, y) => {
              return <tr key={y}>
                {row.map((ea, x) => {
                  return (
                    <Square
                      key={x}
                      x={x}
                      y={y}
                      isAlive={ea}
                      aliveCallback={this.props.alive}
                    />
                  );
                })}
              </tr>;
            })}
          </tbody>
         </table>
      </div>
    );
}

From there you should be able to see how this app would work. App stores the game state and renders the functional component GameBoard. In GameBoard, each Square renders according to its alive value, and triggers an aliveCallback when clicked. aliveCallback should set the state of the appropriate value in the board array inside of App, based on its x and y prop.

4

You can do like

return(
    <td className={`Square ${props.alive ? "active" : "inactive"}`} 
       onClick={() => props.alive()}>
    </td>
  );

Please refer this code

3

Problem from title was not a real reason of ‘not working’

NOTE: This statement

className={props.alive ? “active” : “inactive”}

is correct, using template literals isn’t required.

You can write/use it in many ways:

className={'Square '+ (props.alive ? 'active' : 'inactive')}

To be true there is no need to use ‘inactive’ as ‘Square’ has the same bg color.

className={'Square '+ (props.alive ? 'active' : null)}

and de facto no need for ternary operator

className={'square '+ (props.alive && 'active')}

and of course you can ‘calculate/prepare’ values in plain js before return

const Square = (props) => {
  let classes = ['Square','bb']
  if( props.alive ) classes.push('active')
  classes = classes.join(' ')
  return (
    <h1 className={classes}>Hello</h1>
  )};

Just read docs or google for ‘react css in js’.

LEAVE A COMMENT