Reusable components

We have seen what the best ways to create components are, and the scenarios where it makes sense to use a local state. We have also seen how we can make our components reusable defining a clear interface with the prop types.

Let's now dive into a real-world example and take a look at how we can transform a non-reusable component into a reusable one with a generic, and cleaner, interface.

Suppose we have a component that loads a collection of posts from an API endpoint, and it shows the list on the screen.

It is a simplified example, but it is useful for understanding the necessary steps we need to take to make components reusable.

The component is defined as follows:

  class PostList extends React.Component

With the constructor and a life cycle method, an empty array gets assigned to posts to represent the initial state:

  constructor(props) { 
super(props);

this.state = {
posts: []
};
}

componentDidMount() {
Posts.fetch().then(posts => {
this.setState({ posts });
});
}

During componentDidMount, the API call gets fired, and as soon as the data is available, the posts are stored in the state.

This is a very common data fetching pattern, and we will see the other possible approaches in Chapter 5, Proper Data Fetching.

Posts is a helper class that we use to communicate with the API, and it has a fetch method that returns a promise that gets resolved with a list of posts.

We can now move into the part where the posts are displayed:

  render() { 
return (
<ul>
{this.state.posts.map(post => (
<li key={post.id}>
<h1>{post.title}</h1>
{post.excerpt && <p>{post.excerpt}</p>}
</li>
))}
</ul>
);
}

Inside the render method, we loop through the posts, and we map each one of them into a <li> element.

We assume that the title field is always present, and we show it inside an <h1> element while the excerpt is optional, and we show it inside a paragraph only if it exists.

The preceding component works fine, and it has no problems.

Now, suppose that we need to render a similar list, but this time, we want to display a list of users received from the props type rather than the state (to make clear that we can serve different scenarios):

  const UserList = ({ users }) => ( 
<ul>
{users.map(user => (
<li key={user.id}>
<h1>{user.username}</h1>
{user.bio && <p>{user.bio}</p>}
</li>
))}
</ul>
);

Given a collection of users, the preceding code renders an unordered list very similar to the posts one.

The differences are that the heading, in this case, is the username rather than title and the optional field, that has to be shown only if present, is the bio of the user.

Duplicating the code is usually not the best solution, so let's see how React can help us to keep our code with Don't Repeat Yourself (DRY). The first step to creating a reusable List component is to abstract it a little and decouple it from the data it has to display, and we do that by defining a generic collection property. The main requirement is that, for the posts, we want to display the title and the excerpt, while, for the users, we have to show the username and the bio.

To do this, we create two props: one called titleKeywhere we specify the name of the attribute to be displayed, and one called textKey that we use to specify the optional field.

The props of the new reusable List component are the following:

  import { array, string } from 'prop-types';
...
List.propTypes = {
collection: array,
textKey: string,
titleKey: string,
}

Since the List component is not going to have any state or function, we can write it as a stateless functional component:

  const List = ({ collection, textKey, titleKey }) => ( 
<ul>
{collection.map(item =>
<Item
key={item.id}
text={item[textKey]}
title={item[titleKey]}
/>
)}
</ul>
);

The List component receives the props, and iterates over the collection, mapping all the items into another component (that we are going to create next). As you can see, we are passing to the children titles and text props that represent the values of the main attribute and the optional one, respectively.

The Item component is very simple and clean:

  import { string } from 'prop-types';

const Item = ({ text, title }) => (
<li>
<h1>{title}</h1>
{text && <p>{text}</p>}
</li>
);

Item.propTypes = {
text: string,
title: string
};

So, we've created two components with a well-defined surface area that can we use together to display posts, users or any other kinds of lists. Smaller components are better for several reasons: for example, they are more maintainable and testable, which makes it easier to find and fix bugs.

Great – we can now rewrite our two components, PostsList, and UsersList, to make them use the generic reusable list and avoid duplicating code.

Let's modify the render method of PostsLists as follows:

  render() { 
return (
<List
collection={this.state.posts}
textKey="excerpt"
titleKey="title"
/>
);
}

Then, we will modify the UserList function as follows:

  const UserList = ({ users }) => ( 
<List
collection={users}
textKey="bio"
titleKey="username"
/>
);

We went from a single-purpose component to a reusable one using the props to create a generic and well-defined interface.

It is now possible to reuse this component as many times as we need in our application, and every developer can easily understand how to implement it thanks to the prop types.

We could also go a step further using react-docgen to document our reusable list, as we have seen in the previous section.

The benefits of using a reusable component over a component that is coupled with the data it handles are many.

Suppose, for example, that we want to add logic to hide and show the optional field only when a button is clicked.

Alternatively, perhaps there is a new requirement to add a check and, if the title attribute is longer than twenty-five characters, it gets cut and hyphenated.

We can now make the change at one single point, and all the components that are using it will benefit from the modification.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.171.136