Collins Orlando
Collins Orlando

Reputation: 1681

React app is not reacting to changes as expected. Why?

folks. I'm having a probably noob question, but I'll shoot nonetheless. The code snippets below are fairly straightforward, and I shouldn't be having issues but here we go. I'm trying to get the list of colors in the ColorsGrid component to. In short, when a user changes the difficulty level via the dropdown list, a new set of colors ought to be generated and, thus, displayed. I thought this was a pretty straightforward exercise, but things are not working as expected. Whenever I change the difficulty, it would not react (rerender ColorsGrid component) and only after I once again select another (difficulty) level would the previous one provoke a rerender. For instance, if I were to select Medium after the initial render (default level is set to Easy), nothing changes. However, if I go back to Easy (or select any other difficulty), then does the changes corresponding to the previous (Medium difficulty) occur i.e. ColorsGrid rerenders and, thus, displays a grid corresponding to medium difficulty. What am I doing wrong?

Below is the relevant code.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

// Get random rgb color
function randomColor() {
  let r = Math.floor(Math.random() * 256);
  let g = Math.floor(Math.random() * 256);
  let b = Math.floor(Math.random() * 256);

  return `rgb(${r}, ${g}, ${b})`;
}

// Set length of color list array as a funciton of difficulty 
function colorsListLength(difficulty) {
  switch (true) {
    case difficulty === 'expert':
      return 25;
    case difficulty === 'hard':
      return 20;
    case difficulty === 'medium':
      return 10;
    default:
      return 5;
  }
}

// Get color list array
function colorsList(colorsLength = 5) {
  const colors = [];

  while (colors.length < colorsLength) {
    colors.push(randomColor());
  }

  return colors;
}

// Set random color to guess from (above) color list array
function randomColorToGuess(colors) {
  const index = Math.floor(Math.random() * colors.length);

  return colors[index];
}

// Set number of game tries as a function of difficulty
function numberOfTries(difficulty) {
  switch (true) {
    case difficulty === 'expert' || difficulty == 'hard':
      return 2;
    case difficulty === 'medium':
      return 1;
    default:
      return 0;
  }
}

// Colors grid component
function ColorsGrid({ difficulty, colorsList }) {
  return (
    <div>
      <strong>Colors Grid</strong>
      <p>Difficulty: {difficulty}</p>
      <div>
        {colorsList.length > 0 ? (
          colorsList.map(color => (
            <div
              style={{
                backgroundColor: color,
                height: '3rem',
                width: '3rem',
                borderRadius: '50%',
              }}
              key={color}
            />
          ))
        ) : (
          <div>Loading colors...</div>
        )}
      </div>
    </div>
  );
}

// Main component
class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      difficulty: 'easy',
      colorsList: [],
    };

    this.colorsArray = this.colorsArray.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }

  componentDidMount() {
    this.colorsArray(this.state.difficulty);
  }

  colorsArray() {
    const colors = colorsList(colorsListLength(this.state.difficulty));
    const colorToGuess = randomColorToGuess(colors);

    this.setState(() => ({
      colorsList: colors,
      gameTries: numberOfTries(this.state.difficulty),
      colorToGuess,
    }));
  }

  handleChange(e) {
    this.setState({
      difficulty: e.target.value,
    });

    this.colorsArray(this.state.difficulty); // I was under the impression the (difficulty) state had already been updated here
  }

  render() {
    return (
      <div className="App">
        <h1>Colors</h1>
        <div style={{ textAlign: 'right' }}>
          <select
            id="difficulty"
            value={this.state.difficulty}
            onChange={this.handleChange}
          >
            <option value="easy">Easy</option>
            <option value="medium">Medium</option>
            <option value="hard">Hard</option>
            <option value="expert">Expert</option>
          </select>
        </div>
        <ColorsGrid
          colorsList={this.state.colorsList}
          difficulty={this.state.difficulty}
        />
      </div>
    );
  }
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

Upvotes: 1

Views: 475

Answers (2)

Marcus
Marcus

Reputation: 2321

The issue is with the coordination of your calls to setState. The following cleas things up:

  colorsArray(difficulty) {
    const colors = colorsList(colorsListLength(difficulty));
    const colorToGuess = randomColorToGuess(colors);

    this.setState(() => ({
      difficulty,
      colorsList: colors,
      gameTries: numberOfTries(this.state.difficulty),
      colorToGuess
    }));
  }

  handleChange(e) {
    this.colorsArray(e.target.value);
  }

You can see that your event handler makes one call to the color updating function. This then works out the new colours and sets the state in one place.

Upvotes: -1

Mos&#232; Raguzzini
Mos&#232; Raguzzini

Reputation: 15841

This is because setState() is async:

setState(newState, callback);

In order to get the just selected difficulty you have to change the code like this:

this.setState({
  difficulty: e.target.value,
 }, () => this.colorsArray(this.state.difficulty)
);

Upvotes: 3

Related Questions