Learning React With Create-React-App (Part 3)

Datetime:2016-08-23 00:10:28          Topic: React           Share

Last Updated:08/17/2016

Human, you dare try to feed scraps to Lord of Goats? I wil-oh, hey, these food nuggets are pretty good!

Previous Post In This Series

Current Versions

create-react-app:v0.2.0

react:v15.3.0

Introduction

In the previous tutorial, we started diving deeper into better component separation and an alternate way to define React components with concepts of passed-in properties and interactivity/internal state manipulation. Let’s take things a step further with our HelloWorld components and actually create a dynamic list of components that we can add to! We’ll need to start off by adding a new HelloWorldList that is in charge of rendering our list of HelloWorld components!

Creating HelloWorldList

There’s not really anything new in here if you have been following along, but it’s still very important to step through it and really understand the minutia of creating class components (and when to do so). Let’s create two new files: src/HelloWorldList.js and src/HelloWorldList.css. Inside of src/HelloWorldList.js , we need to start by adding our import statement again from React to also import a named export from the “react” NPM module!

import React, { Component } from 'react';

Next, we write our HelloWorldList class and implement the render() function directly. We’re not worried yet about the state so we don’t have to implement the constructor() function (yet).

class HelloWorldList extends Component {
render() {
return (
<div className="HelloWorldList">
<HelloWorld name="Jim"/>
<HelloWorld name="Sally"/>
</div>
);
}
}

We’ll also add some style to src/HelloWorldList.css just to keep the work clear:

.HelloWorldList {
margin: 20px;
padding: 20px;
border: 2px solid #00D8FF;
background: #DDEEFF;
}

Otherwise, our App component is identical! If we save and reload our code, we should expect to see nothing changed in our browser (but we should also have no error messages)!

Integrating HelloWorldList.js into App.js

We have our list of Hello World components built, but we’ll need to modify our App component to actually use them. Let’s start by changing our import HelloWorld to instead import HelloWorldList:

import HelloWorldList from './HelloWorldList';

And then our render() needs to change to call that new component directly instead of the two HelloWorld components.

const App = () => {
return (
<div className="App">
<HelloWorldList />
</div>
);
};

Our new component should resemble the following:

Clear delineation of each component!

Introducing State To HelloWorldList

Now, we’ll introduce state to our HelloWorldList component that will keep track of our list of greetings that we want to display. Let’s add a constructor first to src/HelloWorldList.js :

constructor(props) {
super(props);
this.state = { greetings: ['Jim', 'Sally'] };
}

Creating And Using A Helper Function

Next, we’ll want a helper function that will return our list of JSX components that is built dynamically from our state! What we want to do is iterate over our list of greetings stored in state (so this.state.greetings ) and for each one of those, render the appropriate HelloWorld component and pass in the name. The operation that we’re performing here is a map operation, which says “loop over the array and call a function for each element in that array, storing the results in a new array”. Let’s look at the implementation for our renderGreetings() function:

renderGreetings() {
return this.state.greetings.map(name => (
<HelloWorld key={name} name={name}/>
));
}

So, map each item in the array to a special anonymous function that just returns a HelloWorld component. Setting the name on your component is something you’ve already seen, but the “key” is new! This is because for React to know which element to modify/remove/etc when one of the elements in your list changes, it has to be able to uniquely identify which element it is, so here we’re just specifying the key as the name.

Note:Since we’re not actually guaranteeing that the list of names is a unique list of names, this will cause issues if we introduce another “Jim” or another “Sally”. We’re just keeping our initial implementation simple for now!

Next, we jump to the render() function and instead of the two <HelloWorld/> calls, we replace it with the following:

render() {
return (
<div className="HelloWorldList">
{this.renderGreetings()}
</div>
);
}

Now save our page and reload and everything should work again! Just to make sure that it is indeed more dynamic, let’s add one more element to the list: ‘Bender’. Back in the constructor:

constructor(props) {
super(props);
this.state = { greetings: ['Jim', 'Sally', 'Bender'] };
}

Now when the component is saved and everything reloads, we should see:

Hello, meatbags!

Even better, our “frenchify” buttons still work exactly the same and exactly as we expect!

Creating A Greeting Adder Component

Let’s make it so that we can make more modifications to our list of components. It’d be nice if we could add someone new to greet every time. Time to create AddGreeter ! Create src/AddGreeter.js and src/AddGreeter.css to start. In src/AddGreeter.css , fill the file with the following:

.AddGreeter {
margin: 20px;
padding: 20px;
border: 2px solid #00FFD8;
background: #DDFFEE;
text-align: center;
}

And then we’ll work on our src/AddGreeter.js file:

import React, { Component } from 'react';
import './AddGreeter.css';

class AddGreeter extends Component {
constructor(props) {
super(props);
this.state = { greetingName: '' };
this.handleUpdate = this.handleUpdate.bind(this);
}
handleUpdate(event) {
this.setState({ greetingName: event.target.value });
}
render() {
return (
<div className="AddGreeter">
<input type="text" onChange={this.handleUpdate}/>

<button>Add</button>
</div>
);
}
}

export default AddGreeter;

This is a lot of the same boiler-plate code you’ve seen a bunch so far. We import React and Component, as well as our component-specific stylesheet. We declare our component and create a constructor for it, accepting props and passing that to the parent. We also set an initial state called “greetingName” and set it to a blank value. We do this so we can rely on our component state instead of trying to fetch values via refs. Since our handleUpdate function will be relying on modifying state and is called via an EventHandler, we need to explicitly bind handleUpdate to the current component.

Next, in our handleUpdate function, we accept in an event and set the state based on that event’s target’s value. This means every time that input is modified, it will trigger this function and update the component’s state based on what is put in/removed from the input.

Finally, in our render function itself, we just include a text input and a button. The button doesn’t do anything yet, but it will! Now, return to src/HelloWorldList.js , import the AddGreeter component, and include it in the render call.

import AddGreeter from './AddGreeter';
# ... bunch of other stuff ...
render() {
return (
<div className="HelloWorldList">
<AddGreeter />
{this.renderGreetings()}
</div>
);
}

Passing Functions Down To Child Components

We have an interesting problem now, however. Our list of greetings is sitting in HelloWorldList , but the component that adds our greetings is a child of HelloWorldList! It has no internal state containing the list of greetings, so how do we make this work?

By creating the function inside of the parent and passing it down to the child via props! Inside of src/HelloWorldList.js , we’ll call our function addGreeting , and it needs to be bound to the correct component, so inside of our constructor, add the following line:

this.addGreeting = this.addGreeting.bind(this);

And then we’re going to add the addGreeting function itself. It should accept a single string as an argument to the function and based on that, add a new element onto the list of greetings. Let’s take a look at the implementation:

addGreeting(newName) {
this.setState({ greetings: [...this.state.greetings, newName] });
}

We have a new ES2015 feature here, which is an array concatenation shortcut. This says “the start of the array should remain this.state.greetings , but I also want you to add newName onto the end of the array. This should return a new modified copy of the array but not change the original.” We set the state equal to this newly-modified array and call it a day. Now, to pass it down to our child, we just pass it like a normal property:

render() {
return (
<div className="HelloWorldList">
<AddGreeter addGreeting={this.addGreeting}/>
{this.renderGreetings()}
</div>
);
}

Now, we need to add our handlers inside of src/AddGreeter.js . We’ll also create an addGreeting function inside of our AddGreeter component. Since we’re doing this and it’ll happen via an event handler, we’ll need to explicitly bind it. Inside of constructor() , add the following line:

this.addGreeting = this.addGreeting.bind(this);

And then start writing the addGreeting function:

addGreeting() {
this.props.addGreeting(this.state.greetingName);
this.setState({ greetingName: '' });
}

This calls the “addGreeting” function that was passed in via props and passes that function (remember the newName argument?) our greetingName out of state. After that, it clears out the greetingName state from our component. Finally, we’ll need to modify our render() function a bit to work with this new function:

render() {
return (
<div className="AddGreeter">
<input
type="text"
onChange={this.handleUpdate}
value={this.state.greetingName}
/>

<button onClick={this.addGreeting}>Add</button>
</div>
);
}

A few things have changed here. First, the input has a new property on it, value , which is set to the current value of “greetingName” from our state! This ensures that when something else clears out our state, such as the function we wrote in the previous example, those changes are reflected appropriately! Finally, our button now has an onClick event handler that just calls the this.addGreeting function that we already defined! Test it out and everything should work as expected!

Way more dynamic, and everything is still nicely self-contained!

Adding A Remove Button

Following the same principal as above, we’ll need to implement a removeGreeting function in the HelloWorldList component and then pass that down to each HelloWorld component that is rendered from our list. We’ll start by implementing the removeGreeting function in src/HelloWorldList.js :

removeGreeting(removeName) {
const filteredGreetings = this.state.greetings.filter(name => {
return name !== removeName;
});
this.setState({ greetings: filteredGreetings });
}

We filter our list of greetings only to greetings that do not match the name of the greeting we want to remove, and set that as our new state of greetings in the list component. Since this is modifying state and being passed to a child, we need to explicitly bind this inside of the constructor:

this.removeGreeting = this.removeGreeting.bind(this);

Finally, our renderGreetings function needs to be updated to pass this function down to each HelloWorld child.

renderGreetings() {
return this.state.greetings.map(name => (
<HelloWorld
key={name}
name={name}
removeGreeting={this.removeGreeting}
/>
));
}

Open up src/HelloWorld.js . Following the same pattern as when we added a function from the parent to the props of the child with AddGreeting , we’re going to implement a removeGreeting function inside of the HelloWorld component. We’ll call our function removeGreeting , so add the bind statement to the constructor:

this.removeGreeting = this.removeGreeting.bind(this);

And then we’ll write the removeGreeting function itself:

removeGreeting() {
this.props.removeGreeting(this.props.name);
}

Finally, inside of our render() function, we’ll add a new button that calls the this.removeGreeting() function in an onClick:

render() {
return (
<div className="HelloWorld">
{this.state.greeting} {this.props.name}!
<br/>
<button onClick={this.frenchify}>Frenchify!</button>
<br/>
<button onClick={this.removeGreeting}>Remove Me!</button>
</div>
);
}

Save your files and start messing around! You can now add and remove components at will and everything is just handled for you!

A completely replaced list of HelloWorld greetings!

Conclusion

Woo! That was definitely a lot more complicated, but now you have a really strong grasp of standard React methods of implementing components, components within components, shared state, etc. Furthermore, we haven’t even broken the confines of create-react-app at all, showcasing what a handy tool it really is! A major motivation for this series of posts is actually to avoid jumping into much more complicated bits of React and add-ons, such as Redux, and try to do as much with vanilla React as we possibly can.

Future posts in this series will cover lifecycle methods and using the two other create-react-app commands: build and eject . We’ll also cover some slightly better code organization techniques!





About List