React Beyond Basics
The Star Match Game

The Star Match Game

I kept the scope and logic of this next game very simple to focus on a few new React concepts. This made the game very simple so that kids would enjoy it. In fact, my first beta testers for this game were my 6 year-old and 4 year-old kids. They love it.

The most important concept to master in this game will be the one-way flow of data needed by children components. We will build the game with only two components to keep the flow simple.

Here’s a screen shot of the final UI for the Star Match game.

picture12

In the left box, the player is challenged with a random number of stars between 1 and 9. They have to pick one or more numbers that sum to that random number of stars. If they do so correctly, the picked number(s) get marked as used (green in the UI). If they pick numbers sum to more than the random number of stars, they get marked as wrong (red in the UI). If they pick a number that is less than the random number of stars, it gets marked as selected (blue in the UI). This last case is okay because they can add more selected numbers to get to a correct answer.

There should always be a correct solution, which means the random number of stars has to depend on the remaining available numbers. If the non-used numbers are only 2 and 4, we can only challenge the player with 2, 4, or 6 stars (but still pick one of these possible values at random). This part is a bit challenging and is purely logic, so I’ll provide a ready utility function to help us with it.

Remember to familiarize yourself with the mechanics of playing the game before you proceed:

Step 1 – Initial Markup and Style

As usual, we’ll start with a mock HTML and CSS template. We need a layout of two main sections, stars in one section, and clickable numbers in the other.

There are many ways to render a star in HTML. You can use SVG or a library like font-awesome, but the simplest way is probably using CSS with the Unicode value for a star, which is 2605.

Note that the font you use to render the UI has to support the Unicode values that you use. Most fonts support the common Unicode values.

The game’s starting HTML/CSS template

Here’s the HTML code that you can start with:

class Game extends React.Component {
  render() {
    return (
      <div className="game">
        <div className="help">
          Pick 1 or more numbers that sum
          to the number of stars
        </div>
        <div className="body">
          <div className="stars">
            <div className="star" />
           {/* Repeat element random X times */}
          </div>
          <div className="play-numbers">
            <button className="number">1</button>
            {/* Numbers 1 to 9 */}
          </div>
        </div>
      </div>
    );
  }
}

ReactDOM.render(<Game />, mountNode);

Here is the CSS for the template:

.game { max-width: 500px; margin: 0 auto; }
.body { display: flex; }
.help { color: #666; margin: 10px; text-align: center; }
.stars {
  width: 50%; text-align: center;
  border: thin solid lightgray;
}
.play-numbers {
  width: 50%; text-align: center; padding: 10px;
  border: thin solid lightgray;
}
.star { display: inline-block; margin: 0 15px;}
.star:after { content: "\2605";  font-size: 45px; }
.number {
  background-color: #eee; border: thin solid #ddd;
  width: 45px; height: 45px;
  margin: 10px; font-size: 25px;
}
.play-number:focus, play-number:active {
  outline: none;  border: thin solid #ddd;
}
.game-done .message {
  font-size: 250%; color: green;
  font-weight: bold; margin: 15px;
}

Step 2 – Making Things Dynamic

We have two main nodes that should be generated dynamically: The 9 play numbers and the random number of stars, which should be between 1 and 9 (to keep the game simple).

Let’s start with the 9 play number. The easiest way to generate them is through a simple loop. However, loops are imperative. The declarative way of doing this is to create an array of 9 unique values and then map that array as play number divs.

We can use the lodash range method to get an array of X numbers.

Do we need this array on the state of the Game component?

Not really. Those numbers will not change during a game session. What changes is their attributes, like is the number used, currently selected as a candidate, or part of a wrong selection.

We can opt to put the numbers and their attributes on the state, but keep in mind that it is always a better strategy to minimize what you put on any component’s state.

We can simply directly generate the numbers array and map it to button elements inside the render function:

<div className="play-numbers">
  {_.range(1, 10).map(number =>
    <button key={number} className="number">
      {number}
    </button>
  )}
</div>

The _.range(1, 10) call generates an array of numbers from 1 to 9. This is the first time we map an array to React elements. Let me explain a few things about that.

React accepts arrays in curly brackets. You can simply render {[1, 2, 3]} anywhere in JSX and React will gladly render that. It’ll use text nodes for every element. Similarly, React accepts arrays of DOM elements. You can test this with:

{[<div>Hello </div>, <div>React</div>]}

React will automatically join this array in the DOM and it will render both elements.

That’s what we did for the play numbers above:

// We mapped this array:
[1, 2, 3, ...]

// Into this array:
[<button />, <button />, <button />, ...]

With that, React will render all 9 buttons in the UI.

Note how I used a key attribute for each button element returned in the map callback. This is a React special attribute that helps uniquely identify each element. This key value is needed whenever you render a dynamic list of children elements and React will actually throw a warning if you don’t specify it. The number value in our case is unique, so we can consider it to be the unique key of each button element.

Always make sure that the key attribute is a unique value that does depend on the order of the elements because that might cause trouble for you down the road.

We can do something similar for the number of stars. However, we need a random value to start with. This game will not have play/correct/wrong UI statuses. It will always be winnable. However, to reset the game for the player to play it again, we’ll have to pick a new and different random number of stars. To keep this last task simple, let’s place the random number of stars on the state of the Game component.

// In the Game class
state = {
  stars: 1 + Math.floor(9 * Math.random()),
};

// In the Game's render method
<div className="stars">
  {_.range(this.state.stars).map(starIndex =>
    <div key={starIndex} className="star" />
  )}
</div>

Now the Game component should render all 9 numbers and a random number of stars on each run.

Playground link for the code so far

Step 3 – Extracting Components

You can do this step before starting to make things dynamic, but sometimes you’ll get clues about which elements should be represented by their own components once you start to dynamically generate them in the UI.

For example, every time you render DOM elements within a map callback function, that is usually a good clue that a component might be needed there.

We rendered both star divs and number buttons within map callbacks. Both are good candidates for components.

Another sign for the need of a component is if the repeated elements are going to share behavior. For example, the play numbers will share the click behavior. Every time a number is clicked, we need to do a few computations on the top-level game scope.

Since we are not going to have any behavior on the stars, I’ll keep them inline, but let’s go ahead and extract the number buttons and render them with a new component. I’ll name it Number.

Do we need a class component to represent this new Number component?

I am going to keep all the state that we need on the Game component, so this new Number component will not have any state. However, it will have a props-driven click handler. By that, I mean a click handler that uses some of the props of that component.

When the player clicks on a number button, we need to have the Game identify which button was clicked. That’s enough reason to go with a class component for Number, because otherwise we would need to generate new functions every time we render a number. More on this later.

I’ll go ahead and add an empty click handler for this new Number component as well and make it report the number that was clicked. Here’s one way to implement that:

class Number extends React.PureComponent {
  clickHandler = () =>
    console.log('Click on ' + this.props.number);

  render() {
    return (
      <button
        className="number"
        onClick={this.clickHandler}
      >
        {this.props.number}
      </button>
    );
  }
}

Then, we can use the Number component within the map callback:

<div className="play-numbers">
  {_.range(1, 10).map(number =>
    <Number key={number} number={number} />
  )}
</div>

Note how the key attribute is now defined on the Number component itself. It’s always needed on the array of elements directly returned from the map call.

Also note how I used React.PureComponent for Number instead of React.Component. A pure component does not always re-render itself when its parent component re-renders. It uses comparison logic to determine if its props/state values are actually different and only re-renders for that case. This means a pure component comes with a tiny extra cost. However, for this game, since we have a Game component that renders 9 Number components and manages a global state object, using a pure component for the children is most likely a good call. More on this later.

Now, clicking on the number buttons should report their numbers in the console.

Playground link for the code so far

Step 4 – Mapping the State to the UI

This is one of the most important steps when building a React app. You need to define the UIs as functions of the app state. However, sometimes the app state is not completely clear from the beginning.

For the Star Match game, there are a few obvious states that we need:

  • Numbers will be selected (for an attempt to sum to the number of stars). The UI should represent them with a blue color. This means we need to place the list of selected numbers on the state.

  • Numbers will be marked used and the UI should represent them with a green color. We’ll need to place the list of used numbers on the state as well.

  • Numbers might be marked as part of a wrong selection and the UI should represent them as red. However, we don’t need to place wrong numbers on the state because we can compute whether the already selected numbers sum up to more than the stars that we have.

I’ll use the names selectedNumbers, usedNumbers, and selectionIsWrong for the 3 state items above. To test our upcoming UI changes, we can place mock data in these variables and test the UI based on what we can expect.

The selectedNumbers and usedNumbers variables belong to the Game state:

// In the Game class

state = {
  stars: 1 + Math.floor(9 * Math.random()),
  selectedNumbers: [2, 4],
  usedNumbers: [7, 8],
};
The new arrays above will eventually start with empty values, but the values I hardcoded for them are for testing purposes only. I use this strategy all the time. Fake it till you make it.

At any point in time, we can compute the selectionIsWrong variable by comparing the selectedNumbers array with the stars that we have. We can use an instance property for this one:

// In the Game class

selectionIsWrong =
  _.sum(this.state.selectedNumbers) > this.state.stars;

The _.sum method is another helpful lodash method that sums the elements of an array. If the sum of all selected numbers is greater than the numbers of the stars we have, the current selection should be marked as wrong.

I want to make the Number component responsible for styling itself based on whether that number is selected, used, or selected while the current selectionIsWrong flag is true. We’ll need to compute something like isSelected and isUsed on each number. We can use a straight array lookup for that with the indexOf array method:

<div className="play-numbers">
  {_.range(1, 10).map((number) => {
    const isUsed =
      this.state.usedNumbers.indexOf(number) >= 0;
    const isSelected =
      this.state.selectedNumbers.indexOf(number) >= 0;

    return (
      <Number
        key={number}
        number={number}
        isUsed={isUsed}
        isSelected={isSelected}
        selectionIsWrong={this.selectionIsWrong}
      />
    );
  })}
</div>

Note how I passed all 3 computed values down to the Number component to make it completely responsible for styling itself.

Now, in the Number component we can have a few conditional statements to determine which background color to use for the button element. Let’s create a style function there to capture all 4 cases of the style logic that we need (red, blue, green, and normal).

Let me actually create a colors object to define which HTML colors to use in the UI. I found lighter versions of the primary HTML colors more suitable for this UI.

const colors = {
  used: 'lightgreen',
  wrong: 'lightcoral',
  selected: 'deepskyblue',
};

This colors object can be a shared variable, or we can design the Game component to accept it as a prop. I’ll keep it globally shared for simplicity.

One cool thing about having a colors configuration object like this is that the logic needed in the component would not have hardcoded values like lightgreen in them. This will make the code a lot more readable.

Here is a possible implementation of the new style function in Number:

// In the Number class

style() {
  if (this.props.isUsed) {
    return { backgroundColor: colors.used };
  }

  if (this.props.isSelected) {
    return {
      backgroundColor: this.props.selectionIsWrong
        ? colors.wrong
        : colors.selected,
    };
  }

  return {};
}

// Then use it in Number's render method
render() {
  return (
    <button
      style={this.style()}
      className="number"
      onClick={this.clickHandler}
    >
      {this.props.number}
    </button>
  );
}

If you test a few renders now, you should see buttons 7 and 8 used (green) and buttons 4 and 2 either selected (blue) or wrong (red) based on the random number of stars that get generated.

Playground link for the code so far

Step 5 – Selecting and Using Numbers

Every time the player clicks on a number button, we need to update something on the Game state. Let’s do the logic analysis:

  • We first need to place the clicked number in the selectedNumbers array. This makes it blue.

  • If the newly selected number is less than the current number of stars, that’s all the update we need. The player can select more numbers and if the sum of all selected numbers is still less that the current number of stars, we still only update the selectedNumbers array.

  • When the sum of selectedNumbers exactly matches the current number of stars, we need to mark all numbers in selectedNumbers as used (by placing them in the usedNumbers array) and reset the selectedNumbers array. We also need to draw a new random number of stars at this point.

  • If the sum of selectedNumbers becomes greater than the number of stars, we need to update the selectionIsWrong flag to true.

Can we implement all of these conditions with a single setState call? Remember that you need to treat setState as asynchronous.

Yes, we can. We can use the updater function syntax for setState to get access to the previous state, implement all the conditional statements above in that function and return just one object to merge with the state. Here’s a possible implementation for a function to do that.

// In the Game class

onNumberClick = number => {
  this.setState(prevState => {
    let {
      selectedNumbers,
      usedNumbers,
      stars,
    } = prevState;

    selectedNumbers = [...selectedNumbers, number];

    const selectedSum = _.sum(selectedNumbers);

    if (selectedSum === stars) { // Correct picks
      usedNumbers = [...usedNumbers, ...selectedNumbers];
      selectedNumbers = [];
      stars = 1 + Math.floor(9 * Math.random());
    }

    this.selectionIsWrong = selectedSum > this.state.stars;

    return {
      selectedNumbers,
      usedNumbers,
      stars,
    };
  });
};

When we render the Number component, we pass this new handler to it:

// In the Game's render method

<Number
  key={number}
  number={number}
  isUsed={isUsed}
  isSelected={isSelected
  selectionIsWrong={this.selectionIsWrong}
  onClick={this.onNumberClick}
/>

The key thing to learn about the code above is that instead of having multiple if branches that each invoke a call to setState, we should use the if branches to prepare an object and invoke setState once if possible. This is usually possible if the multiple setState operations can happen at the exact same moment (no delay is needed between them).

At this point you can reset the fake testing values in selectedNumbers and usedNumbers. We don’t need them anymore. Both elements should start with an empty array.

Now all we need to do in the Number component is invoke the new onClick property that the Game component is now passing to it. We need to change the clickHandler function:

// In Number

clickHandler = () => {
  if (!this.props.isUsed) {
    this.props.onClick(this.props.number);
  }
};

Note that this.props.onClick is just a function and we just invoked it with the clicked number’s value as its argument. Also note that I added an edge-check here to disable clicking any used numbers. Since we already have the isUsed computed flag, we can simply use that.

Now, clicking on numbers should turn them blue, green, or red based on the number of stars. Each time a green set is marked, the game should render a new random number of stars. However, there is no connection yet between the number of stars and the number available to play. So this game might not be winnable in most cases. Let’s fix that next.

Playground link for the code so far

Step 6 – Making the Game Always Winnable

Before we fix the random star-generation problem, let me tease your mind with a question: With the code we have so far, if a player goes through a full game and picks all numbers successfully (so 9 clicks), how many arrays would this React app have generated in memory?

The answer is: Way too many. At least 20.

Why?

Every time the state of the Game component changes (which happens with each button click), the Game’s `render method is executed and we are generating 2 arrays there using the lodash range function. One array for the stars and one array for the numbers.

So, 2 times 9 clicks + 1 initial render = 20 generated arrays.

This is bad.

This is a common problem that you will face all the time in React apps. You need to generate some objects and use them in the render method (like the 2 arrays we have above). However, once generated, those objects will not change for the entire lifetime of the component. The worst place to do the generation is within the render method.

You should create such objects during the construction of a component instance and just define them as instance properties. This way during the lifetime of a component the generation would happen only once.

However, this means when it is time to reset the component (for example, to make this game playable again), you will need to redo the generation (because it is no longer happening in render). Don’t forget that.

Alternatively, you can perform the generation in render the first time and cache it after that using an instance property. I prefer to be explicit about initially generating the instance property and then clearly resetting it when needed.

Let’s simply place the array of numbers and the array of stars on the Game component instance.

Furthermore, we previously placed the stars count on the state of the Game component, but we did not need to. The number of stars also does not change with every click. We only need to generate a new stars array if the player picks a correct set of numbers. This is another example where we can eliminate the unnecessary generation of objects by leveraging the caching on component instances. However, for the stars array we have to refresh the generation more frequently while for the numbers array we need a refresh only when the game is completely reset.

Minimize what you place on the components state as much as you can. Use instance caching where you can, but always keep in mind that any cached values will eventually need to be refreshed at some point.
// In the Game class

numbers = _.range(1, 10);
stars   = _.range(1 + Math.floor(9 * Math.random()));

// Then in render
// use this.numbers.map and this.stars.map

Another thing I changed above is having both numbers and stars as arrays instead of what we had before when stars was just a number on the state. This is about consistency between similar interfaces and objects that have related logic.

To make the game always winnable we need 2 main modifications:

  • We can’t use a regular Math.random() to draw a new set of stars. We have to use the available numbers and pick a random sum from within all the possible permutations of these available numbers. This is a famous array logic problem, but it is beyond the scope of this book. We’ll use a utility function for this.

  • We need to implement an undo feature to unselect numbers when the player selects a wrong set. This one is straight forward. We just need to replace 1 line in numberClick. Can you identify that line?

In numberClick, the line that pushes the clicked number to the selected numbers array needs to be replaced with an if statement to either add or remove the number. All the remaining logic is the same (except for the new random number of stars).

Here’s a possible implementation of that if statement:

if (selectedNumbers.indexOf(number) >= 0) {
  // Unselect already selected number
  selectedNumbers = selectedNumbers.filter(sn => sn !== number);
} else {
  // The previous logic
  selectedNumbers = [...selectedNumbers, number];
}
By the way, prior to doing this unselect feature, the game had a bug where a selected number can be selected again and contribute to a correct or wrong UI change. Did you notice that in your testing? If you did not, you want to keep in mind that your testing is probably biased toward happy cases and just because it works does not mean it’s right.

Here’s a utility function that we can use to fix the random number of stars problem:

const randomSum = (arr, maxSum) => {
  const sets = [[]], sums = [];
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0, len = sets.length; j < len; j++) {
      const candidateSet = sets[j].concat(arr[i]);
      const candidateSum = _.sum(candidateSet);
      if (candidateSum <= maxSum) {
        sets.push(candidateSet);
        sums.push(candidateSum);
      }
    }
  }
  return _.sample(sums);
}

The randomSum function above receives an array of numbers and a maxSum value. It will compute all the permutation sets of the numbers array, compute the sum for each permutation set, and return a random sum from the list of all sums that are less than or equal to maxSum. This is exactly what we need for picking a new random number of stars.

We just need to compute the list of available numbers which is the array difference between the original list of 9 numbers and the list of usedNumbers. Luckily, lodash has an array difference function that we can use for this logic. Here’s a possible implementation to fix the stars generation:

if (selectedSum === this.stars.length) { // Correct picks
  usedNumbers = [...usedNumbers, ...selectedNumbers];
  selectedNumbers = [];
  this.stars = _.range(
    randomSum(_.difference(this.numbers, usedNumbers), 9)
  );
}

Note how we still need to generate an array from the newly picked random sum. Also note that I am refreshing the value of stars on the component instance and no longer managing that on the Game state. Since we are updating the state anyway (for usedNumbers and selectedNumbers), React will trigger a UI update and display the new random number of stars.

Now that we have a randomSum function, we can use it instead of Math.random to also pick the first random number of stars. While this is not needed, it would make the game favor larger sums at the beginning, which I think is a good enhancement:

// In the Game class
numbers = _.range(1, 10);
stars = _.range(randomSum(this.numbers, 9));

The Game should now be always winnable.

Playground link for the code so far

Step 7 – Adding a Play-again Feature

With the changes we did in Step 6 above, when a game is done, there will be no available numbers to pick a random sum from and the game will render no stars.

picture13

Let’s add a Play Again button in that now empty space and make that button reset the game. We need to reset both the state and any computed cached values we have on the instance.

However, we did not introduce any game status to determine if the game is done or not. We can certainly check if we don’t have any more stars but having an instance flag dedicated to this status will make the code more readable. I’ll name it gameIsDone.

The game is done if the length of the used numbers array equals the length of the original numbers array. We can add this logic after the other logic we have in the numberClick function:

// In numberClick

this.gameIsDone = usedNumbers.length === this.numbers.length;

Then, we can have the UI render the play-again button if gameIsDone is true and the random stars otherwise.

When there is a branching statement that would render different trees within a component’s render method, I like to create 2 new methods for the 2 rendering branches. I’ll name these methods renderStars and renderPlayAgain. The latter will render a simple button that will reset the game.

To reset the game, we need to generate a new random number of stars, reset the newly set gameIsDone flag, and re-initialize the state with empty arrays.

Here are the 3 new methods that we need:

// In the Game class

renderStars() {
  return this.stars.map(starIndex =>
    <div className="star" key={starIndex} />
  );
}

renderPlayAgain() {
  return (
    <div className="game-done">
      <div className="message">Nice!</div>
      <button onClick={this.resetGame}>Play Again</button>
    </div>
  );
}

resetGame = () => {
  this.stars = _.range(randomSum(this.numbers, 9));
  this.gameIsDone = false;
  this.setState({
    selectedNumbers: [],
    usedNumbers: [],
  });
};

Then, we can use the new render methods within the main render method:

// In the Game's render method

<div className="stars">
  {this.gameIsDone
    ? this.renderPlayAgain()
    : this.renderStars()}
</div>

With these changes, when a game is done, a Play Again button should show up and clicking it will reset the game for the player to play again.

Playground link for the code so far

We can now check the MVP-complete box! However, there is an important thing about this game that you are yet to learn. I have planted an important problem in the code that we have to talk about now.

Were you able to figure this problem out on your own? Study the code and try to find it if not. This is not a "bug" problem. It is a React bad practice that will cause it to do unnecessary work just like the generations of arrays that we fixed in Step 6. However, this time the problem is in the heart of React data flow.

Step 8 – Optimizing Measurable Issues

The golden rule of optimization is: if you can’t measure the problem, don’t optimize anything about it.

Luckily, when it comes to React components it is really easy to measure their performance. React is all JavaScript, so the Performance tab in Chrome dev-tools (or its equivalent) is your best friend. React has hooks for browsers DevTools to make them report more helpful information about React apps. For example, if you request your app with a ?react_perf query string, the Performance timeline will show a new User Timing section detailing the creating, mounting and updating timings of each component in the app. This is very helpful.

picture14

One of the common performance problems in React apps is the concept of wasteful renders. This happens when a component re-renders itself unnecessarily. For example, with Parent/Child components in a simple app, if the Parent component gets re-rendered, the Child component will get re-rendered as well even if the props/state values of the rendered Child did not change.

React will only re-render the Child component for this case. It will not update its associated DOM elements, so it is not a big deal. However, when we are dealing with multiple children components, this becomes more important.

Whenever React re-renders a component after a props/state update, it will trigger the componentDidUpdate lifecycle method. We can use this method to find out how frequently a component update is happening.

For example, in the Star Match game code that we have so far, let’s find how frequently the Number component gets updated by adding this code:

// In the Number class

componentDidUpdate() {
  console.log('Number updated');
}

Then play a few rounds and record your observations.

On some clicks, more than 1 Number component gets updated. Sometimes, all 9 Number components get updated although React only needed to update 1 Number (the clicked one). The other 8 renders were simply wasted.

This should not happen. The Number component is a pure one because it extended React.PureComponent. This simply means if the props/state of a Number component are exactly the same between parent renders, the pure component should not re-render.

This is a sign that we are passing more information to the Number component than what it needs to render itself. We should fix this. Only 1 Number component should get updated per click.

The Number component does not have any state, so this problem is in one of the props that we pass to it. Which one though?

Here’s how you can find out. On each update, inspect the current and next props and see which props are different in the components that should not get updated. You can use either componentWillUpdate or componentDidUpdate to do this inspection. For example:

// In the Number class

componentWillUpdate(nextProps) {
  console.log(this.props, nextProps);
}

Now, when you play and click a number, each Number component that is updated will report its props before and after the update. Here’s a screenshot of my console after clicking the number 2 for a fresh game session:

picture15

Note how isSelected changed only in the number 2 click above. This is correct. However, also note how selectionIsWrong changed from undefined to false in all clicks. This change is bad. It was not needed. A quick fix for this one is to initialize selectionIsWrong as false in the Game component.

// In the Game class

selectionIsWrong = false;

With just this simple change, here’s what happens when I clicked the number 2 on a fresh game:

picture16

Only the number 2 component was updated. The rest of the Number components were not updated because none of their props changed.

However, this is just one case. We still have a problem of wasteful renders. Try to figure it out.

If the player clicks a number greater than the current number of stars, all 9 Number components are updated. See the screenshot below to see the output from the lifecycle methods. Can you tell why this is happening?

picture17

It is because clicking a wrong number makes selectionIsWrong change to true and we are passing selectionIsWrong to the Number component. This makes them all re-render.

This is a bad design. The Number component does not need to be aware of whether the global selection is wrong. It just needs to know whether it should render itself as wrong. Let’s fix that.

Instead of selectionIsWrong, we can pass a simple isWrong prop to Number and that should be true only for the number(s) that actually need to be displayed as wrong, not for all the numbers. This means we have to change the logic conditions in the Number component a bit.

We now need a separate condition for isWrong:

style() {
  if (this.props.isUsed) {
    return {
      backgroundColor: colors.used,
    };
  }
  if (this.props.isWrong) {
    return {
      backgroundColor: colors.wrong,
    };
  }
  if (this.props.isSelected) {
    return {
      backgroundColor: colors.selected
    };
  }

  return {};
}

This will fix the last wasteful renders case that we found. However, I think we can do better.

The many if statements in the style function above scream for a refactor. If only we match the props names with keys on the colors object, we can probably get rid of all these if statements.

If you think about it, a Number can only have one color. Maybe instead of passing isUsed, isWrong, and isSelected, we can pass a status prop that will have one of 4 values: available, selected, wrong, and used. If we do that, we can just lookup the needed color directly!

There is nothing wrong with keeping the logic to determine the color in the Number component as long as we pass the minimal information needed for that logic. However, lifting the logic to the Game component makes the Number component a bit more generic. For example, we can use it with different status values in a different app if we needed to.

If we have that status prop, the style function in number will be reduced to:

style() {
  return { backgroundColor: colors[this.props.status] };
}

This is so much better. I wouldn’t even keep this logic in a function. An inline version of it is okay:

<button
  style={{ backgroundColor: colors[this.props.status] }}
  className="number"
  onClick={this.clickHandler}
>
  {this.props.number}
</button>

I had to add an available key to colors to keep the lookup consistent:

const colors = {
  available: '#eee',
  used: 'lightgreen',
  wrong: 'lightcoral',
  selected: 'deepskyblue',
};

We also need to slightly modify the Number component clickHandler function because we no longer have an isUsed prop.

clickHandler = () => {
  if (this.props.status !== 'used'){
    this.props.onClick(this.props.number);
  }
};
If you want the Number component to be more reusable, instead of depending on the used string value in the clickHandler, you can pass it a disabled flag prop to make it block its clicking behavior. I’ll leave this one for you to figure out.

Here’s an implementation of a Game level function to compute any number status:

// In the Game class

numberStatus(number) {
  if (this.state.usedNumbers.indexOf(number) >= 0) {
    return 'used';
  }

  const isSelected =
    this.state.selectedNumbers.indexOf(number) >= 0;

  if (isSelected) {
    return this.selectionIsWrong ? 'wrong' : 'selected';
  }

  return 'available';
}

We can use that directly when rendering a Number component:

// In the Game's render method

<div className="play-numbers">
  {this.numbers.map((number) => (
    <Number
      key={number}
      number={number}
      status={this.numberStatus(number)}
      onClick={this.onNumberClick}
    />
  ))}
</div>

I don’t know about you, but I certainly prefer this version to the previous one. The previous version has computations in many places while this one packs all status computations into one neat little function that we can test on its own.

Don’t forget to remove the code we used to test the rendering in the Number component lifecycle methods.

I think this game is now in good shape for some user testing.

Playground link for the final code of this game

Bonus Challenges

Make this game a bit more interesting by adding a few features * Come up with a timeout value and force the player to pick all 9 numbers within that value. If they took longer than the timeout value, show a "Game Over" message instead of the "Nice" message that I used with the play-again feature. * Keep a score for each win and maybe make that score depend on how fast the player picks all 9 numbers.

A version of this game was featured in my Pluralsight course, React.js: Getting Started. I covered a few different concepts in the online course.