Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript Refactor the Consumers

Hello, How could we add highScore icon to this version of our app? Thanks

In the previous tutorials there was a scoreboard app with the 'HighScore' icon functionality. Here it's missing. Could you please help me to add one here too? I tried really hard but could no solve it out. Many thanks

John Moran
John Moran
7,950 Points

I had the same problem, I managed to get it working using:

Context/index.js

//put the function before the render
getHighScore = () => {
    const scores = this.state.players.map(p => p.score);
    const highScore = Math.max(...scores);
    if (highScore > 0) {
      return highScore;
    }
    return null;
  };
//in the render()
actions: {
            changeScore: this.handleScoreChange,
            removePlayer: this.handleRemovePlayer,
            addPlayer: this.handleAddPlayer,
            highScore: this.getHighScore
          }

Player.js

<Icon isHighScore={score === context.actions.highScore()} />

Icon.js

 <svg
      viewBox="0 0 44 35"
      className={props.isHighScore ? "is-high-score" : null}
    >

Because we are now using {this.props.children} in the index.js, made it confusing for me, as well as using this Higher Order Component concept for the first time. I got the solution by debugging the props value using console.log.

1 Answer

Jamie Gobeille
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Jamie Gobeille
Full Stack JavaScript Techdegree Graduate 19,573 Points

Hello! I was able to get mine set up like this:

context/index.js

import React, { Component } from "react";

const ScoreboardContext = React.createContext();

//higher order component
export class Provider extends Component {
  state = {
    players: [
      {
        name: "Guil",
        id: 1,
        score: 0,
      },
      {
        name: "Treasure",
        id: 2,
        score: 0,
      },
      {
        name: "Ashley",
        id: 3,
        score: 0,
      },
      {
        name: "James",
        id: 4,
        score: 0,
      },
    ],
  };
  //
  prevPlayerId = 4;

  /* handle Leader method*/
  //
  getHighScore = () => {
    //maps through all the scores
    const scores = this.state.players.map((p) => p.score);
    console.log(scores);
    // puts the highest scores into a variable
    const highScore = Math.max(...scores);
    console.log(highScore);
    if (highScore) {
      return highScore;
    }
    return null;
  };

  handleScoreChange = (index, delta) => {
    this.setState((prevState) => {
      // New 'players' array – a copy of the previous `players` state
      const updatedPlayers = [...prevState.players];

      // A copy of the player object we're targeting
      const updatedPlayer = { ...updatedPlayers[index] };

      // Update the target player's score
      updatedPlayer.score += delta;
      // Update the 'players' array with the target player's latest score
      updatedPlayers[index] = updatedPlayer;
      this.getHighScore();

      // Update the `players` state without mutating the original state
      return {
        players: updatedPlayers,
      };
    });
  };

  handleRemovePlayer = (id) => {
    this.setState((prevState) => {
      return {
        players: prevState.players.filter((p) => p.id !== id),
      };
    });
  };

  handleAddPlayer = (name) => {
    this.setState((prevState) => {
      return {
        players: [
          //adds in all the previous players~
          ...prevState.players,
          {
            name,
            score: 0,
            id: this.prevPlayerId + 1,
          },
        ],
      };
    });
  };

  render() {
    return (
      //The provider tag no longer exists because it was replaced with a class called provider
      //To remedy that, you need to reference the context created inside this index.js file
      <ScoreboardContext.Provider
        //You can also pass functions to be consumed.
        //Just pass the values as an object and assign the values properties that can be called
        value={{
          players: this.state.players,
          actions: {
            changeScore: this.handleScoreChange,
            removePlayer: this.handleRemovePlayer,
            addPlayer: this.handleAddPlayer,
            highScore: this.getHighScore(),
          },
        }}
      >
        {this.props.children}
      </ScoreboardContext.Provider>
    );
  }
}

export const Consumer = ScoreboardContext.Consumer;

Player.js

//Importing PureComponent, a special property from React library
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import Counter from "./Counter";
import Icon from "./Icon";
import { Consumer } from "./Context";

//When extending a PureComponent, it only will only re-render a specific player
//component ONLY when it has a change in its props is detected

//Previously, if there was a change to one players props, then it unnecessarily re-rendered all the players again

class Player extends PureComponent {
  static propTypes = {
    index: PropTypes.number,
    isHighScore: PropTypes.bool,
  };

  render() {
    const { index, isHighScore } = this.props;
    return (
      <div className="player">
        <Consumer>
          {({ actions, players }) => (
            <span className="player-name">
              <button
                className="remove-player"
                onClick={() => actions.removePlayer(players[index].id)}
              >
                βœ–
              </button>
              {/** Highest Score passed into the Player.js file from App.js as a prop */}
              <Icon isHighScore={isHighScore} /> {/** Crown Icon */}{" "}
              {/** true or false */}
              {players[index].name}
            </span>
          )}
        </Consumer>

        <Counter index={index} />
      </div>
    );
  }
}

export default Player;

Playerlist.js

import React from "react";
import { Consumer } from "./Context";
import Player from "./Player";

const PlayerList = () => {
  return (
    <Consumer>
      {({ players, actions }) => (
        <React.Fragment>
          {players.map((player, index) => (
            <Player
              key={player.id.toString()}
              index={index}
              isHighScore={actions.highScore === player.score}
            />
          ))}
        </React.Fragment>
      )}
    </Consumer>
  );
};

export default PlayerList;

icon.js

import React from "react";
import PropTypes from "prop-types";

//creates SVG icon
const Icon = ({ isHighScore }) => {
  return (
    <svg viewBox="0 0 44 35" className={isHighScore ? "is-high-score" : null}>
      <path
        d="M26.7616 10.6207L21.8192 0L16.9973 10.5603C15.3699 14.1207 10.9096 15.2672 7.77534 12.9741L0 7.24138L6.56986 28.8448H37.0685L43.5781 7.72414L35.7425 13.0948C32.6685 15.2672 28.3288 14.0603 26.7616 10.6207Z"
        transform="translate(0 0.301727)"
      />
      <rect
        width="30.4986"
        height="3.07759"
        transform="translate(6.56987 31.5603)"
      />
    </svg>
  );
};

Icon.propTypes = {
  isHighScore: PropTypes.bool,
};

export default Icon;

Basically, I just had to add the function to provider, then use it in the Playerlist.js file and pass the true or false result of isHighScore={actions.highScore === player.score} through the rest of the app as a prop.

Let me know if that makes sense!