Should we think about the reusability of our code all the time?
Some advise us to think about it only after we face the need for it. That means if we're creating a brand new component and don't know whether it will be used somewhere else, then we shouldn't try to make it reusable because that complicates our life (think "analysis-paralysis" and "premature optimization").
After all - we can't know the future, you know?
Well, I disagree with that!
I believe we should plan our work and set up the scene correctly so we can later improve it more easily. In real life, nobody likes to touch stuff that "works well as is," especially when there is a deadline and a bad legacy "smell."
So the tendency is to write "new and better stuff"…
Like it's supposed to be better just because it's newly created. But the harsh reality is that most of us are far from perfection, and what's good enough today will invariably become a "legacy" that needs to be improved by refactoring or replacing.
In Object Oriented Programming, the SOLID principles guide programmers and developers on how to plan (or refactor) their software for better design.
Although we'll talk about functional React components, a big chunk of the "philosophy" behind SOLID is also applicable in our case, so I strongly encourage you to investigate this topic further.
The main takeaway is that we can and should plan to create understandable, testable, maintainable, and reusable code.
It never happens by chance…
In a minute, we'll slice and dice one hardly reusable chunk of code into elegant React components, but before that, I will share with you my strategy for producing better results that I learned from a famous novelist.
What Can We Learn About Coding From Hemingway?
I'm totally convinced that making software is creative writing on a higher plane. So we can learn a lot about the process of "writing" from the great novelists of the past.
Hemingway once said to an aspiring writer:
"The first draft of anything is rubbish."
It's true in coding, too. Whenever I try to come up with the perfect component or function, that slows me down and makes me anxious.
That's why I always start with a "first draft."
I make the app to show me the component.
It should look as designed. And it should behave as planned. I don't think about quality, reusability, or testability in this phase. I write what comes first to my mind.
When it's on my screen, I switch to my editor again and start pondering how to make it better…
Let's take this for example:
import React, { useEffect, useState } from 'react';
export default function FeaturedPostsSection() {
const [posts, setPosts] = useState([]);
useEffect( () => {
fetch("https://buhalbu.com/api/blog/featured")
.then(response => response.json())
.then(featuredPosts => setPosts(posts))
.catch(error => console.log(error.message));
}, []);
return (
<section className="blog-featured-posts">
<div className="container">
{posts.map(({slug, featuredMedia, category, path, title, author}) =>
<div key={slug} className="snippet">
{featuredMedia && <div className="snippet-img">
<img
src={featuredMedia.url}
alt={title}
width={428}
height={285}
/></div>}
<div className="snippet-img-overlay">
<a className="badge">{category.name}</a>
<a href={path}>
<h5 className="snippet-title">
{title}
</h5>
</a>
<p className="snippet-text">{author.name}</p>
</div>
</div>
)}
</div>
</section>
)
}
The purpose of this component is to show a set of featured blog posts on top of the main archive page. It's not so reusable because it doesn't accept any props meaning that you can't customize it dynamically. Also, it's tightly coupled to my API and data structure.
If I don't need precisely the same featured post snippets somewhere else, then I can't reuse the section.
But no worry - it's just a shitty first draft.
How can we make it better?
After analyzing it briefly, I can see eight stand-alone React components.
Let's start with the easiest ones:
import React from 'react';
//First Step
const SnippetText = ({children}) => {
return <p className="snippet-text">{children}</p>;
}
//Second Step
const SnippetText = (props) => {
return (<p
{...props}
className={`snippet-text ${props.className}`}
>
{props.children}
</p>);
};
//Third Step
const SnippetText = React.forwardRef(
(props, ref) => {
return (<p
ref={ref}
{...props}
className={`snippet-text ${props.className}`}
>
{props.children}
</p>);
}
);
SnippetText.displayName = 'SnippetText';
export default SnippetText;
import React from 'react';
//First Step
export const Badge = ({children, href}) => {
return <a href={href} className="badge">{children}</p>;
}
//Second Step
export const Badge = (props) => {
return (<a
{...props}
className={`badge ${props.className}`}
>
{props.children}
</a>);
};
// Third Step
export const Badge = React.forwardRef(
(props, ref) => {
return (<a
ref={ref}
{...props}
className={`badge ${props.className}`}
>
{props.children}
</a>);
}
);
Badge.displayName = 'Badge';
export default Badge;
The components above are just styled HTML elements. By extracting them, we save ourselves from the effort of typing more code than needed. They are not a big win but illustrate the process well.
In step 1, we copy-paste the markup into its functional component;
In step 2, we adapt the component to accept all kinds of props that an HTML paragraph or anchor can take;
In step 3, we forward a reference (if any);
Let's see more:
import React from 'react';
export const SnippetTitle = React.forwardRef(
(props, ref) => {
const Title = props.as || 'h5';
return (
<a
ref={ref}
{...props}
className={`snippet-title ${props.className}`}
>
<Title>{props.children}</Title>
</a>
);
});
SnippetTitle.displayName = 'SnippetTitle';
export default SnippetTitle;
This is also a simple one - it's the clickable post title that leads to the full text. As you might notice, I've made it slightly more reusable by adding the "as" property. That way, the title is not always H5, and it no longer has to be a heading at all.
And what about this one:
import React from 'react';
export const SnippetImgOverlay = React.forwardRef(
(props, ref) => {
return (<div
ref={ref}
{...props}
className={`snippet-img-overlay ${props.className}`}
>
{props.children}
</div>);
});
SnippetImgOverlay.displayName = 'SnippetImgOverlay';
export default SnippetImgOverlay;
Here we extracted the container that holds the badge, the title, and the author's name. Now we can create an alternative element that doesn't make an overlay but places the three inner components below the image.
import React from 'react';
export const SnippetBody = React.forwardRef(
(props, ref) => {
return (<div
ref={ref}
{...props}
className={`snippet-body ${props.className}`}
>
{props.children}
</div>);
});
SnippetBody.displayName = 'SnippetBody';
export default SnippetBody;
Nice. Now let's extract the featured images into their own component:
import React from 'react';
const SnippetImage = React.forwardRef(
(props, ref) => {
const {src, alt, width, height, className} = props;
return (
<div
ref={ref}
{...props}
className={`snippet-img ${className}`}>
<img
src={src}
alt={alt}
width={width}
height={height}
/>
</div>
);
});
SnippetImage.displayName = 'SnippetImage';
export default SnippetImage;
Ok, let's stop for a moment and put into use the new React goodies we've created.
What would the first code snippet look like?
import React, { useEffect, useState } from 'react';
export default function FeaturedPostsSection() {
const [posts, setPosts] = useState([]);
useEffect( () => {
fetch("https://buhalbu.com/api/blog/featured")
.then(response => response.json())
.then(featuredPosts => setPosts(posts))
.catch(error => console.log(error.message));
}, []);
return (
<section className="blog-featured-posts">
<div className="container">
{posts.map(({slug, featuredMedia, category, path, title, author}) =>
<div key={slug} className="snippet">
{featuredMedia && <SnippetImage
src={featuredMedia.url}
alt={title}
width={428}
height={285}
/>}
<SnippetImgOverlay>
<Badge>{category.name}</Badge>
<SnippetTitle href={path}>{title}</SnippetTitle>
<SnippetText>{author.name}</SnippetText>
</SnippetImgOverlay>
</div>
)}
</div>
</section>
)
}
It looks a lot better, doesn't it?
But we can extract at least two more components: One named Snippet that will wrap all the other snippet parts and one called "Section" that will hold a set of snippets.
Let's do it:
import React from 'react';
export const Snippet = React.forwardRef((props, ref) => {
return <div ref={ref} {...props} className="snippet">{children}</div>;
});
Snippet.displayName = 'Snippet';
export default Snippet;
import React from 'react';
export const Section = React.forwardRef((props, ref) => {
return (
<section ref={ref} {...props} >
<div>
{props.children}
</div>
</section>
);
});
Section.displayName = 'Section';
export default Section;
But why I'm creating this section component when I have FeaturedPostsSection?
Well, I want to decouple the presentation logic from the business logic. You may already know that there are three general types of components: Container, Presentation, and Higher-Order Components.
In our case, I'm extracting the JSX markup into its own functional component so it can be reused elsewhere.
For example, suppose I don't want to feature on my homepage blog posts but on website pages. In that case, I can create a new container component named "FeaturedPagesSection" and put all the other stuff we made without substantial changes.
Let's see the final result of our work:
import React, { useEffect, useState } from 'react';
export default function FeaturedPostsSection() {
const [posts, setPosts] = useState([]);
useEffect( () => {
fetch("https://buhalbu.com/api/blog/featured")
.then(response => response.json())
.then(featuredPosts => setPosts(featuredPosts))
.catch(error => console.log(error.message));
}, []);
return (
<Section className="blog-featured-posts">
{posts.map(({slug, featuredMedia, category, path, title, author}) =>
<Snippet key={slug}>
{featuredMedia && <SnippetImage
src={featuredMedia.url}
alt={title}
width={428}
height={285}
/>}
<SnippetImgOverlay>
<Badge>{category.name}</Badge>
<SnippetTitle href={path}>{title}</SnippetTitle>
<SnippetText>{author.name}</SnippetText>
</SnippetImgOverlay>
</Snippet>
)}
</Section>
)
}
Well, that looks fine for now. It's better than the initial code snippet. Maybe, in the feature, we'll need to improve it, but I consider it "good enough."
Brief Overview of The Applied Principles
So it took us 20 or 30 minutes more to transform a bad first draft into something that will make our life easier in the future.
Why?
Because we have got a couple of ready-for-reuse components, meaning we will write less code to achieve similar results. And all of them allow easier further improvements.
We put into use three general principles:
- Separation of Concerns - We split the big component into several others of three types: Container, Presentation, and Higher-Order Components.
- From Specific to Abstract - We climbed one or two steps up on the ladder of Abstraction, meaning we took some ultra-specific components (PostSnippetTitle, FeaturedPostsSection) and made them more abstract (SnippetTitle, Section), that way we can use them with different content entities (Posts, Pages, Products, Services)
- Flatten Props - We chose to pass data through stand-alone properties and not as plain objects, that way, you know at first glance what data the component needs, and it's more convenient.
I hope you liked this tutorial. Please don't hesitate to ask in the comment section below if you have any questions.
Want to Connect?
If you enjoyed this article, there is some chance to enjoy the other free stuff I write on advanced JavaScript topics for my newsletter, too.
Check it out :)
Top comments (0)