Back to Schedule

Week 6 Lab: Laser Tag Leaderboard Part 2

Notes from Lecture on March 7: Notes

In this week’s lab, we will be adding interactivity to our leaderboard so that we can add new players to the Individual Leaderboard via the Add Player form. We will first need to add events and state to our form to process the form values. Then we will need to lift this state up to the Main component and communicate it to the IndividualLeaderboard so that it can add a row for the new player.

We will focus on the form submission during class today, and assign populating the Individual Leaderboard for homework which will be due on Tuesday, March 13 at 6pm. As always, if you submit early, you will get a chance for a code review and to make changes before the final submission. Since this will take quite a few steps, there will be no additional prep for next week.


Step 1: Handle changes in form input values

To get a sense of how we can use events in JSX with form elements to submit form data, use the following guide: Forms in React.

Following the example in the “Handling Multiple Inputs” section, we will first establish a state in our AddPlayer component that consists of the values from each of the form fields. This includes player name, score, or whichever fields you are recording for your particular leaderboard case. We then need to add bind a handleInputChange function to our component and attach it to the onChange events on each input to track the changes in values. In handleInputChange, we should update the corresponding values in state with this.setState. Remember to bind your handleInputChange function to this in the constructor or use an arrow function!

You may notice in the example function, that we are setting state on [name]:

handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
        [name]: value
    });
}

That object notation is shorthand for something like this, where the property named name is set:

const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;

const propertiesToSet = {};
propertiesToSet[name] = value;
this.setState(propertiesToSet);

Since event.target refers the the DOM element that the event happened on, the name that comes from event.target is simply the name attribute of the input, e.g. foo in:

<input type="text" name="foo" />

So the foo property of state would be set there. To use this structure for your needs, simply assign a name to each input equivalent to the property you want to track that input’s value in this.state. You can check that your state is updating with changes in each of the inputs in the React Inspector.


Step 2: Send form data to parent component on submit

The next step will be to send the new player information from the AddPlayer form to the Main component when the submit button is clicked.

Why do we send the data to Main instead of IndividualLeaderboard directly? The answer is that we don’t have an immediate link between AddPlayer and IndividualLeaderboard, but both are children of Main. Moreover, we will eventually need the player information in both IndividualLeaderboard and TeamLeaderboard, so the state of Main will be a great place to store the information about players, so that it can be shared across the child components.

We will want something like this to establish players as a property of state in Main, and include a method to add additional players to the list:

class Main extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            players: []
        };

        this.addToPlayers = this.addToPlayers.bind(this);
    }

    addToPlayers(player) {
        // Make a copy of players array and concat new player because we don't want to edit a state property 
        // directly in React
        const players = this.state.players.concat(player);
        this.setState({
            players: players
        });
    }

    render() {
        // Your render method here
        // ...
    }
}

Here, players is a list of player objects that each consist of the player fields we have defined and added to the state of AddPlayer. In AddPlayer component, define a function that will be called when the form is submitted and have that function add the current player data by using addToPlayers from Main. If you need hints on how to lift state upward to a parent component, refer back to the “Lifting State Up to Parent Components” section in the prep from this week: Prep 6

In your form submission callback, you may need to call event.preventDefault() to prevent the page from refreshing when the form is submitted. The “default” behavior of form submission is to refresh the page, but we don’t want that to happen here, so we call preventDefault to stop the page from refreshing.

Again, check in React Inspector in the Main component that the players property in state is being set as expected.

Please also make sure to clear the form fields after the form is submitted.


Homework: Individual Leaderboard

Step 1: Display players as rows in Individual Leaderboard

We now need to display the data for each player as a row in the IndividualLeaderboard component. Refer to the “Passing State Down to Child Components” section in Prep 6 for instructions on how to do this.

In the render() method of IndividualLeaderboard, you will need to translate the list of players (passed in as a prop from Main), to row elements in the table. Recall that the .map() function (described in Prep from this week) can help with rendering out the contents of an array.

You may get an error about each resulting element not having a unique key. Recall that React requires that all identical components or JSX elements be assigned a unique key property. When dealing with arrays, we can often use the index in the array as a key. The index is available as the second argument to the function passed to map, for instance:

{this.props.players.map((player, index) => {
    return (<Component key={index} player={player} />);
})}

(Replace Component there with your code for rendering out a row for the player)

When the render function in IndividualLeaderboard successfully processes the players’ data to table rows, you should see the new players from the AddPlayer form being added to the table as they are submitted.


Step 2: Rank the players in Individual Leaderboard

The final step is to sort the players by rank. Luckily, JavaScript provides a built in function to sort arrays that can be used on the fly, similar to .map. The function is called .sort and can be used in conjunction with .map as so:

{this.props.players
    .sort((playerA, playerB) => {
        return playerA.score < playerB.score;
    })
    .map((player, index) => {
        return (<Component key={index} rank={index + 1} player={player} />);
    });
}

What this does is say, when comparing playerA and playerB, make playerA show up earlier in the resulting list if playerA has a higher score than playerB. Here’s some more information on the sort syntax: Sort

We can also now use index + 1 from the map as the rank of the player to display in the rank column.


By the end of these instructions, you should have a working Add Player form that adds player data to the Individual Leaderboard section in the ranked order. Please submit this as Lab 6 to GitHub.

Solutions: Lab 6 Solutions