TIL/2021–02–01&02

Day 53 and 54: Treehouse Full Stack JavaScript Techdegree

Unidirectional Data Flow

In React data naturally flows down the component tree, from the app’s top level component down to the child components via props

For example, in our scoreboard, the App component tells the player component all about a player by passing information via props, it also tells the header component the total number of players

If our data comes from one place, React will flow any data changes from the top going down to the component tree, updating each component

And this is called unidirectional data flow

— — —

Lifting State Up

In React, two or more components can share the same state. For example, in our App component, the players state is shared to the Header and Player components through props.

Our Counter component has the score state which can only be accessed locally within the counter component since that is where we set the state. Eventually, we need access to the score state to determine who has the highest score and such

When two or more components needs access to the same state, we move the state into the common parent which is called lifting state up.

So we will lift up the score state to the app component which is the common parent

— —

Communicating between components

The buttons in the counter need to modify the state that lives in the app component

When the data flow is unidirectional (data changes goes from top to bottom) how does the child component like counter gets information back up to its ancestor parent component? Instead of passing state to a child component, a parent can pass a callback function

The callback will allow you to communicate events and changes in your data upwards, while data continues to flow downwards

We can write event handlers that manipulate state and pass them down to components as callback functions

On app.js

import React, { Component } from “react”;

import Header from “./Header.js”;

import Player from “./Player.js”;

class App extends Component {

state = {

players: [

{

name: “Guil”,

score: 0,

id: 1,

},

{

name: “Treasure”,

score: 0,

id: 2,

},

{

name: “Ashley”,

score: 0,

id: 3,

},

{

name: “James”,

score: 0,

id: 4,

},

],

};

handleRemovePlayer = (id) => {

this.setState((prevState) => {

return {

players: prevState.players.filter((p) => p.id !== id),

};

});

};

handleScoreChange = (delta) => {

// this.setState((prevState) => ({

// score: prevState.score + delta,

// }));

console.log(delta);

};

render() {

return (

<div className=”scoreboard”>

<Header title=”Scoreboard” totalPlayers={this.state.players.length} />

{/* Players list */}

{this.state.players.map((player) => (

<Player

{…player}

handleScoreChange={this.handleScoreChange}

key={player.id.toString()}

removePlayer={this.handleRemovePlayer}

/>

))}

</div>

);

}

}

export default App;

Then on Player.js add the handleScoreChange as a prop to pass on to counter

Counter

score={props.score}

handleScoreChange={props.handleScoreChange}

/>

Then on Counter

We can add an onClick event handler with a callback function to handleScoreChange

import React from “react”;

const Counter = (props) => {

return (

<div className=”counter”>

<button

className=”counter-action decrement”

onClick={() => {

props.handleScoreChange(-1);

}}

>

{“ “}

-{“ “}

</button>

<span className=”counter-score”>{props.score}</span>

<button

className=”counter-action increment”

onClick={() => props.handleScoreChange(+1)}

>

{“ “}

+{“ “}

</button>

</div>

);

};

export default Counter;

Don’t forget, it has to be an arrow function to be a callback

— —

Update state based on a player’s index

The map callback function takes an optional index parameter that contains the index of the current item being processed in the array

handleScoreChange = (delta, index) => {

// this.setState((prevState) => ({

// score: prevState.score + delta,

// }));

console.log(`index:${index}, delta:${delta}`);

};

{this.state.players.map((player, index) => (

<Player

index={index}

{…player}

handleScoreChange={this.handleScoreChange}

key={player.id.toString()}

removePlayer={this.handleRemovePlayer}

/>

))}

On Player

<Counter

index={props.index}

score={props.score}

handleScoreChange={props.handleScoreChange}

/>

On Counter

const Counter = (props) => {

const index = props.index;

return (

<div className=”counter”>

<button

className=”counter-action decrement”

onClick={() => {

props.handleScoreChange(-1, index);

}}

>

{“ “}

-{“ “}

</button>

<span className=”counter-score”>{props.score}</span>

<button

className=”counter-action increment”

onClick={() => props.handleScoreChange(+1, index)}

>

{“ “}

+{“ “}

</button>

</div>

);

};

Now each player logs their distinct index

handleScoreChange = (delta, index) => {

this.setState((prevState) => ({

score: (prevState.players[index].score += delta),

}));

};

And now it works

— — -

Building the statistics component

The problem with out old structure is that the score state was kept locally in the Counter component — and if we need access to the total scores we cannot do that. But since we were able to lift up the state to the common or main parent, any component can have access to the score state.

On app.js

<Header title=”Scoreboard” players={this.state.players} />

On header.js

import React from “react”;

import Stats from “./Stats”;

const Header = (props) => {

return (

<header>

<Stats players={props.players} />

<h1>{props.title}</h1>

</header>

);

};

export default Header;

Then the newly created Stat.js

import React from “react”;

const Stats = (props) => {

const totalPlayers = props.players.length;

const totalPoints = props.players.reduce((total, player) => {

return (total += player.score);

}, 0);

return (

<table className=”stats”>

<tbody>

<tr>

<td>Players:</td>

<td>{totalPlayers}</td>

</tr>

<tr>

<td>Total Points:</td>

<td>{totalPoints}</td>

</tr>

</tbody>

</table>

);

};

export default Stats;

— — —

Controlled components

Adding players

For JSX, JSX requires input tags to be a self-closing element

In React, we need to handle a form element’s state explicitly.

Normally, when a user types into an input text field the user changes the state of the text field

To manage our input field’s state we have to build a controlled component — a controlled component renders a form that controls what happens in that form on subsequent user input.

It is a form element whose value is controlled by React with a state.

Creating a controlled component:

  1. Initialize state for the value of the input
  2. Listen for changes on the input to detect when the value is updated
  3. Create an event handler that updates the value state

import React, { Component } from “react”;

class AddPlayerForm extends Component {

state = {

value: “”,

};

handleValueChange = (e) => {

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

};

render() {

console.log(this.state.value);

return (

<form>

<input

type=”text”

value={this.state.value}

onChange={this.handleValueChange}

placeholder=”Enter a player’s name”

/>

<input type=”submit” value=”Add Player” />

</form>

);

}

}

export default AddPlayerForm;

The state updates as we type

We now have a controlled input, meaning that we manually managed the value state of the component

— — —

Adding Items to State

Currently the AddPlayerForm component has no access to the players state in App component and the AddPlayerForm state is a local state

In order to add a new player to the state, AddPlayerForm needs access to the players state. So that it can update it with the submitted data

If we don’t do .preventDefault() — it will result in the browser posting a request back to the server. That would cause our application to reload in the browser — meaning we would lose the data we just typed

addPlayerForm.js

import React, { Component } from “react”;

class AddPlayerForm extends Component {

state = {

value: “”,

};

handleValueChange = (e) => {

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

};

handleSubmit = (e) => {

e.preventDefault();

this.props.addPlayer(this.state.value);

this.state.value = “”;

//or use this.setState({value: ‘’})

};

render() {

return (

<form onSubmit={this.handleSubmit}>

<input

type=”text”

value={this.state.value}

onChange={this.handleValueChange}

placeholder=”Enter a player’s name”

/>

<input type=”submit” value=”Add Player” />

</form>

);

}

}

export default AddPlayerForm;

App.js

handleAddPlayer = (name) => {

this.setState({

players: [

this.state.players, //Represents all existing players on the scoreboard. We are just adding the following object

{

name: name,

score: 0,

id: this.state.players.length + 1,

},

],

});

};

<AddPlayerForm addPlayer={this.handleAddPlayer} />

We used the spread operator to unpack the array and then add the new player

— — — -

Update the players state based on the previous state

handleAddPlayer = (name) => {

this.setState((prevState) => {

return {

players: [

…prevState.players,

{

name: name,

score: 0,

id: this.state.players.length + 1,

},

],

};

});

};

handleAddPlayer = (name) => {

const newPlayer = {

name,

score: 0,

id: this.state.players.length + 1,

};

this.setState((prevState) => {

return {

players: prevState.players.concat(newPlayer),

};

});

};

If not return

handleAddPlayer = (name) => {

const newPlayer = {

name,

score: 0,

id: this.state.players.length + 1,

};

this.setState((prevState) => ({

players: prevState.players.concat(newPlayer),

}));

};

By just using the () parenthesis in replacement of the curly brackets {}

— — -

React flows any data changes at the top down through the component tree updating each component

Can two or more components access and share the same state? Yes. A parent component can pass state down to its children via props

When two or more components need access to the same state, we hoist the state into their common parent — lifting state

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store