Update: Part 2 of this post, featuring context, has been published! Feel free to skip part 1 if you are already familiar with React basics.
Introduction
Background
Since the release of React 16, we have been introduced to quite a few new features. There’s even more coming up, but some new features that are out of beta are already enough to keep our minds full.
Specifically, I want to talk about context, a feature that has the potential to completely upend React development.
To be completely clear, context is not exactly a new feature. In fact, context has been around for a while (libraries like React Redux and React Router have been using it). But until now it was labeled as a scary “experimental” feature. Now that it is a fully-fledged, first-class feature of the React library, there’s no better opportunity to explore how to incorporate it into our toolkit and rethink many assumptions we have about best practices.
But first, some React review.
Overview
Throughout the remaining post and the next, we will go through a simple React app, progressively add some small features, and pursue some refactors. If I’ve done a somewhat decent job, you’ll appreciate how context can make things better, especially once the size of an application increases.
In this first part, as a React review, we’ll see how far we can just using React’s state. We’ll then modify the app to add a new feature. Then, we’ll go about refactoring and modularizing our small app, using props along the way.
While this may be review for certain people, this post assumes some prior knowledge of React. If this is not you, it might help to read up on React basics, particularly props and state. Come back to this post after! It will still be here, I swear.
Additionally, this post makes use of JavaSceipt ES6+ features. Specifically, object destructuring assignment.
Setup
If you would like to code along, clone the repo and check out to the 0-react-state
branch. The app is bootstrapped using create-react-app.
In the directory run:
npm install
#or
yarn install
Then:
npm start
#or
yarn start
State
We can use React state to deal with concerns that are local to the current component. This is helpful for things such as inputs and buttons that modify values that we are using. When the user takes an action in the UI we can alter state, and thus update the UI.
With not very much code we can do something that would be a lot more cumbersome without a framework like React. In our simple blogging app, the user can upvote the post by clicking the button we defined in our render method. An upvote will then be added using .addUpVote()
through state via React Component’s.setState()
method.
// ./src/App.js
import React, { Component } from 'react';
import './App.css';
class App extends Component {
state = {
title: 'My Very First Blog Post',
content: `Vim ex mucius tincidunt, at quo justo ceteros facilisis, te erat offendit cum.
Errem oportere cu nam. An tale modus omittantur per, sed fierent detracto ne.
Semper inermis reprimique an mei, qui at probo illum accumsan.
Id quo quod tincidunt scriptorem, et solet prodesset sea.
Sea an ullum similique interesset.`,
upvotes: 0,
}
addUpVote = () => {
this.setState({
upvotes: this.state.upvotes + 1,
})
}
render() {
const {
title,
content,
upvotes,
} = this.state;
return (
<div className="main">
<div className="post">
<header className="post__header">
<h1 className="post__title">{title}</h1>
</header>
<p className="post__body">
{content}
</p>
<button
className="post__plus-one-button"
onClick={this.addUpVote}>
+ {upvotes}
</button>
</div>
</div>
);
}
}
export default App;
Though it is neat that React makes this easy for us, this is not very interesting so far. Moreover, we’re providing two values to state (title
and content
) that we aren’t even modifying at all! ? Let’s dig a little deeper and give the user the ability to edit the post.
First, we add two items to the state that mirror the title and content, titleDirty
and contentDirty
. We also include an input and textarea to hold this state. This serves as the form for the user to edit the post.
titleDirty
and contentDirty
will allow us to store, in real-time, the modified values for title
and content
without losing the values that they have initially been set to.
// ./src/App.js
import React, { Component } from 'react';
import './App.css';
class App extends Component {
state = {
title: 'My Very First Blog Post',
content: 'Vim ex mucius tincidunt, at quo justo ceteros facilisis, te erat offendit cum.' +
'Errem oportere cu nam. An tale modus omittantur per, sed fierent detracto ne.' +
'Semper inermis reprimique an mei, qui at probo illum accumsan.' +
'Id quo quod tincidunt scriptorem, et solet prodesset sea.' +
'Sea an ullum similique interesset.',
upvotes: 0,
titleDirty: 'My Very First Blog Post',
contentDirty: 'Vim ex mucius tincidunt, at quo justo ceteros facilisis, te erat offendit cum.' +
'Errem oportere cu nam. An tale modus omittantur per, sed fierent detracto ne.' +
'Semper inermis reprimique an mei, qui at probo illum accumsan.' +
'Id quo quod tincidunt scriptorem, et solet prodesset sea.' +
'Sea an ullum similique interesset.',
}
addUpVote = () => {
this.setState({
upvotes: this.state.upvotes + 1,
})
}
render() {
const {
title,
content,
titleDirty,
contentDirty,
upvotes,
} = this.state;
return (
<div className="main">
<div className="post">
<header className="post__header">
<h1 className="post__title">{title}</h1>
</header>
<p className="post__body">
{content}
</p>
<button
className="button"
onClick={this.addUpVote}>
+ {upvotes}
</button>
</div>
<div className="edit-post">
<label>Title:</label>
<input
className="edit-post__input"
type="text"
name="titleDirty"
label="Title"
value={titleDirty}
/>
<label>Content:</label>
<textarea
className="edit-post__content"
type="text"
name="contentDirty"
value={contentDirty}
/>
</div>
</div>
);
}
}
export default App;
With that set up, we want to save our new post right? Not too fast. ✋ First, we need to update the dirty states when the forms’ values change.
// ./src/App.js
/* ... */
addUpVote = () => {
this.setState({
upvotes: this.state.upvotes + 1,
})
}
handleInputChange = (event) => {
/*
Since we provided our input and textarea elements
with name props that correspond to the "dirty"
items in our state, we can use event.target.name
as our key when we call `.setState()`. This saves
us from having to write separate methods to handle
the input and textarea changes separately.
*/
const {
value,
name,
} = event.target;
this.setState({
[name]: value
});
}
render() {
/* ... */
<div className="edit-post">
<label>Title:</label>
<input
className="edit-post__input"
type="text"
name="titleDirty"
label="Title"
value={titleDirty}
onChange={this.handleInputChange}
/>
<label>Content:</label>
<textarea
className="edit-post__content"
type="text"
name="contentDirty"
value={contentDirty}
onChange={this.handleInputChange}
/>
</div>
/* ... */
With that in place, we can now add the logic to save our edited post!
// ./src/App.js
/* ... */
handleInputChange = (event) => {
const {
value,
name,
} = event.target;
this.setState({
[name]: value
});
}
savePost = () => {
this.setState({
title: this.state.titleDirty,
content: this.state.contentDirty,
})
}
render() {
/* ... */
/* We add a save button after the textarea */
<label>Content:</label>
<textarea
className="edit-post__content"
type="text"
name="contentDirty"
value={contentDirty}
onChange={this.handleInputChange}
/>
<button
className="button"
onClick={this.savePost}>
Save Post
</button>
</div>
</div>
);
}
}
export default App;
When we are ready to save the post, .savePost()
will overwrite our title
and content
with their “dirty” versions. Not bad!
Reflection
This is neat, and we can actually accomplish a lot using just state. But what if we wanted to add multiple posts? What about even just adding more elements to the page, like a user status or comment area and button. You can probably see how this how this could get cumbersome quickly. Time to modularize, and introduce handy props along the way.
Props, part 1
Once our apps get larger we will need to modularize our components. Yet we may still have concerns and functionality that encompass several of our components. In this case, we can use props to pass down any properties (an object, array, function, any valid JS value) we’d like from a parent to custom child components that we make.
To start off, it might be useful to check out to the 1-edit-form-with-state
branch as a starting point. This will bring us up to the point we’ve gotten, plus add a bunch of styles that will make your app look better!
First, let’s create a component called Post.js
to contain the blog post that was previously living in our main App file, using the functional component style.
This Post component reads from four props that are sent to it as an input. Three of the props contain values (title
, content
, and upvotes
) and one is our function addUpVote()
.
//./src/Post.js
import React from 'react';
const Post = (props) => (
<div className="post">
<header className="post__header">
<h1 className="post__title">{props.title}</h1>
</header>
<p className="post__body">
{props.content}
</p>
<button
className="button"
onClick={props.addUpVote}>
+ {props.upvotes}
</button>
</div>
);
export default Post;
Now we need to import our Post to the main App component and update our render method to use it.
// ./src/App.js
import React, { Component } from 'react';
import './App.css';
import Post from './Post';
// no changes here
render() {
return (
<div className="main">
<Post
title={this.state.title}
content={this.state.content}
upvotes={this.state.upvotes}
addUpVote={this.addUpVote}
/>
<div className="edit-post">
<label>Title:</label>
<input
className="edit-post__input"
type="text"
name="titleDirty"
label="Title"
value={titleDirty}
/>
<label>Content:</label>
<textarea
className="edit-post__content"
type="text"
name="contentDirty"
value={contentDirty}
/>
</div>
</div>
);
}
// no changes here
After confirming that our app loads again and everything is working dandy, we will also separate out the edit post section to its own file.
//./src/EditForm.js
import React, { Component } from 'react';
class EditForm extends Component {
state = {
title: this.props.title,
content: this.props.content,
}
handleInputChange = (event) => {
const {
value,
name,
} = event.target;
this.setState({
[name]: value
});
}
render() {
const {
savePost
} = this.props;
const {
title,
content
} = this.state;
return (
<div className="edit-post">
<label>Title:</label>
<input
className="edit-post__input"
type="text"
name="title"
label="Title"
value={title}
onChange={this.handleInputChange}
/>
<label>Content:</label>
<textarea
className="edit-post__content"
type="text"
name="content"
value={content}
onChange={this.handleInputChange}
/>
<button
className="button"
onClick={() => {
savePost({ title, content })
}}>
Save Post
</button>
</div>
)
}
}
export default EditForm;
As you can probably tell, this is a tiny bit more complex than our Post component. We can walk through it.
First, we give the EditPost its own local state to hold the title
and content
state values that are being modified. These are what were formerly titleDirty
and contentDirty
when they were living in our App component. Here, these values are initialized as the title and content props that are passed down from the App component as props.
The idea is that once the user is done editing we can call savePost
which comes from a prop passed down from App.js
.
Admittedly, we definitely didn’t have to do this. We could have just kept titleDirty
and contentDirty
in App.js
and have its savePost
method just read those values from its own state. But one of the goals of this exercise is to encourage optimization and modularization. Doing this will allow us to make our App.js
root component a lot cleaner.
Finally, we can update App.js
to use our new edit form.
// ./src/App.js
import React, { Component } from 'react';
import './App.css';
import Post from './Post';
import EditForm from './EditForm';
// no changes here
render() {
return (
<div className="main">
<Post
title={this.state.title}
content={this.state.content}
upvotes={this.state.upvotes}
addUpVote={this.addUpVote}
/>
<EditForm
title={this.state.title}
content={this.state.content}
savePost={this.savePost}
/>
</div>
);
}
}
Conclusion
That’s it for this part. If this was a review to you, I hope you found it to be an adequate summary of some of the core aspects of React. If, on the other hand, some of the concepts were a bit over your head. I encourage you to go over the very clear React docs on these subjects (state, and props).
Recent Comments