Learning React With Create-React-App (Part 3)
Last Updated: 11/17/2017
Previous Post In This Series
Current Versions
create-react-app: v1.4.3
react: v16.1.1
react-scripts: v1.0.17
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! We also need to import our HelloWorld component since we’ll be referencing that inside of this file!
import React, { Component } from 'react';
import './HelloWorldList.css';import HelloWorld from './HelloWorld';
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). Don’t forget the default export at the end of this file either!
class HelloWorldList extends Component {
render() {
return (
<div className="HelloWorldList">
<HelloWorld name="Jim"/>
<HelloWorld name="Sally"/>
</div>
);
}
}export default HelloWorldList;
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:
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:
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!
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!
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.
Check out my new books!
Hey everyone! If you liked what you read here and want to learn more with me, check out my new book on using the latest version of Create React App:
This covers everything you need to know to become proficient using Create React App v2 to become a better, more productive front-end developer, and really dive deep into the details of Create React App all while building a new React project from scratch!
And, of course, my Phoenix Web Development book is also still available if you want to learn more about Elixir web development:
I’m really excited to finally be bringing this project to the world! It’s written in the same style as my other tutorials where we will be building the scaffold of a full project from start to finish, even covering some of the trickier topics like file uploads, Twitter/Google OAuth logins, and APIs!