Get Started with Modern React: Step by Step
S02・V07: Extracting Components
- We will split larger components into smaller components, to improve component reusability.
- We will show that renaming props can help decouple an extracted component.
Setup
Our starting point is as follows (the App
component has not been defined yet):
import React from "react";
import ReactDOM from "react-dom";
ReactDOM.render(<App />, document.getElementById("root"));
Let’s say we have some data representing a “comment” on a blog. This comment
has a date
field, a text
field, and an author
field, which is an object consisting of name
and avatarUrl
fields.
import React from "react";
import ReactDOM from "react-dom";
const comment = { date: new Date(), text: "I hope you enjoy learning React!", author: { name: "Hello Kitty", avatarUrl: "https://placekitten.com/g/64/64" }};
ReactDOM.render(<App />, document.getElementById("root"));
Let’s also create a small utility function that takes a date, and returns a localized string representing the date.
function formatDate(date) {
return date.toLocaleDateString();
}
Adding a ‘Comment’ Component
Now, we can set up a Comment
function component. The Comment
component will return an outer div
. The div
will receive a CSS class
attribute. As we are writing JSX and not HTML, the CSS class
attribute becomes a JSX className
prop, written in camel-case.
We will pass the className
prop the string “Comment”. Within this outer div
, we will put three other div
tags. The first will have the className
of UserInfo
. The second will have the className
of Comment-text
. And the third will have the className
of Comment-date
.
import React from "react";
import ReactDOM from "react-dom";
const comment = {
date: new Date(),
text: "I hope you enjoy learning React!",
author: {
name: "Hello Kitty",
avatarUrl: "https://placekitten.com/g/64/64"
}
};
function formatDate(date) {
return date.toLocaleDateString();
}
function Comment(props) { return ( <div className="Comment"> <div className="UserInfo"></div> <div className="Comment-text"></div> <div className="Comment-date"></div> </div> );}
ReactDOM.render(<App />, document.getElementById("root"));
Adding a Top-Level ‘App’ Component
Before we add more to the Comment
component, let’s first add our top-level App
component. It will return the Comment
component. We will pass in three props to the returned Comment
component, which will get their values from the comment
data object. First, the date
prop. Second, the text
prop. Third, the author
prop. Now, the Comment
component has a props
object which contains date
, text
, and author
data passed down into it.
import React from "react";
import ReactDOM from "react-dom";
const comment = {
date: new Date(),
text: "I hope you enjoy learning React!",
author: {
name: "Hello Kitty",
avatarUrl: "https://placekitten.com/g/64/64"
}
};
function formatDate(date) {
return date.toLocaleDateString();
}
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo"></div>
<div className="Comment-text"></div>
<div className="Comment-date"></div>
</div>
);
}
function App() { return ( <Comment date={comment.date} text={comment.text} author={comment.author} /> );}
ReactDOM.render(<App />, document.getElementById("root"));
We can demonstrate this by adding a console.log()
of the props
object to the Comment
component before the return
expression.
function Comment(props) {
console.log('props:', props); return (
<div className="Comment">
<div className="UserInfo"></div>
<div className="Comment-text"></div>
<div className="Comment-date"></div>
</div>
);
}
We can now open the JavaScript Console, and then reload the page to see the printed object.
We will use the JSON.stringify()
method to pretty-print the props
object.
function Comment(props) {
console.log('props:', JSON.stringify(props, null, 2)); return (
<div className="Comment">
<div className="UserInfo"></div>
<div className="Comment-text"></div>
<div className="Comment-date"></div>
</div>
);
}
You can see that the props
object has date, text, and author information within it.
Adding an Image
Now, within the UserInfo
div, we will add in img
. Within the img
, we will add a className
of Avatar
. img
s need a src
attribute, so we will add a src
prop, which gets its data from the props
object passed in to the component. And, each img
should have an alt
attribute, to which we will pass in the author’s name.
function Comment(props) {
console.log('props:', JSON.stringify(props, null, 2));
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar" src={props.author.avatarUrl} alt={props.author.name} /> </div>
<div className="Comment-text"></div>
<div className="Comment-date"></div>
</div>
);
}
The image shows on our page.
Underneath the image, we will create another div
, with className
of UserInfo-name
. Inside this div
, we will show the author’s name. The author’s name now shows on our page. Now, we will add the comment text. The comment text now shows on our page. Finally, we will add the comment date, using the formatDate()
utility function.
function Comment(props) {
console.log('props:', JSON.stringify(props, null, 2));
return (
<div className="Comment">
<div className="UserInfo">
<img
className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name} </div>
</div>
<div className="Comment-text">
{props.text} </div>
<div className="Comment-date">
{formatDate(props.date)} </div>
</div>
);
}
The comment date now shows on our page. The Comment
component is now complete.
Extracting ‘Avatar’ Component
However, the Comment
component can be broken up into smaller and simpler components. First, let’s extract a component that will show the avatar image. We will call the component Avatar
. Now, we will move the img
from the Comment
component into the Avatar
component.
function Avatar(props) {
return (
<img
className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
);
}
Now, we can refer to the Avatar
component in its place, making sure we pass in an author
prop.
function Comment(props) {
console.log('props:', JSON.stringify(props, null, 2));
return (
<div className="Comment">
<div className="UserInfo">
<Avatar author={props.author} /> <div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
Save the changes, and note that the page is unchanged. We have successfully extracted the Avatar
component.
Renaming Props
Note that the Avatar
component doesn’t need to know that it is being rendered inside a Comment
component, so the prop name author
can be renamed to something more generic. We can rename the author
prop to user
.
function Avatar(props) {
return (
<img
className="Avatar"
src={props.user.avatarUrl} alt={props.user.name} />
);
}
function Comment(props) {
console.log('props:', JSON.stringify(props, null, 2));
return (
<div className="Comment">
<div className="UserInfo">
<Avatar user={props.author} /> <div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
This simple refactoring makes the Avatar
component less coupled to the Comment
component, and therefore more reusable. We recommend naming props from the component’s own point of view rather than the context in which it is being used.
Extracting ‘UserInfo’ Component
Next, we will extract a UserInfo
component that renders an Avatar
next to the user’s name. We will move the user info div
from the Comment
component to the UserInfo
component. Let’s not forget to rename author
to user
within the UserInfo
component. Now, we can refer to the UserInfo
component in its place, making sure we pass in an user
prop.
import React from "react";
import ReactDOM from "react-dom";
const comment = {
date: new Date(),
text: "I hope you enjoy learning React!",
author: {
name: "Hello Kitty",
avatarUrl: "https://placekitten.com/g/64/64"
}
};
function formatDate(date) {
return date.toLocaleDateString();
}
function Avatar(props) {
return (
<img
className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
function UserInfo(props) { return ( <div className="UserInfo"> <Avatar user={props.user} /> <div className="UserInfo-name"> {props.user.name} </div> </div> );}
function Comment(props) {
console.log('props:', JSON.stringify(props, null, 2));
return (
<div className="Comment">
<UserInfo user={props.author} /> <div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
function App() {
return (
<Comment
date={comment.date}
text={comment.text}
author={comment.author}
/>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
Save the file and make sure the page is unchanged after our refactoring.
Extracting components might seem like grunt work at first, but having a palette of reusable components pays off in larger apps. A good rule of thumb is that if a part of your UI is used several times, or is complex enough on its own, it is a good candidate to be a reusable component.
Summary
We split larger components into smaller components, to improve component reusability. And, we showed that renaming props can help decouple an extracted component.
Next Up…
In the next video, we will look at component state.