React Patterns — Exploring common design patterns like HOCs, render props, and compound components

Abiodun Ajayi
8 min readOct 17, 2023

These days, ReactJs is a globally popular View library. Therefore, it’s crucial to implement the best architectural practices to enhance code reusability, maintainability, and readability.

Nonetheless, since React only handles the application’s view layer and doesn’t impose any particular architecture like MVC or MVVM, it may lead to complexities in managing your code base as your React project expands.

This article will teach you the best architectural practices to implement in your code when using ReactJS to create a reusable library.

Directory Layout

Originally, the styling and the code for our components were separated. All styles lived in a shared CSS file (we use SCSS for preprocessing). The actual component (in this case FilterSlider), was decoupled from the styles:

├── components
│ └── FilterSlider
│ ├── __tests__
│ │ └── FilterSlider-test.js
│ └── FilterSlider.jsx
└── styles
└── photo-editor-sdk.scss

Over multiple refactorings, it was experienced that this approach didn’t scale very well. In the future, our components need to be shared between multiple internal projects, like the SDK and an experimental text tool we’re currently developing. So we switched to a component-centric file layout:

components
└── FilterSlider
├── __tests__
│ └── FilterSlider-test.js
├── FilterSlider.jsx
└── FilterSlider.scss

The idea was that all the code that belongs to a component (e.g. JavaScript, CSS, assets, tests) is located in a single folder. This makes it very easy to extract the code into an npm module or, in case you’re in a hurry, to simply share the folder with another project.

Component Imports

One of the drawbacks of this directory structure is that importing components requires you to import the fully qualified path, like so:

import FilterSlider from 'components/FilterSlider/FilterSlider'

But what we’d really like to write is this:

import FilterSlider from 'components/FilterSlider'

The naive approach to solve this problem is to change the component file to index.js:

components
└── FilterSlider
├── __tests__
│ └── FilterSlider-test.js
├── FilterSlider.scss
└── index.jsx

Unfortunately, when debugging React components in Chrome and an error occurs, the debugger will show you a lot of index.js files, and that made this option a no-go.

Another approach we tried was the directory-named-webpack-plugin. This plugin creates a little rule in the webpack resolver to look for a JavaScript or JSX file with the same name as the directory from which it’s being imported. The drawback of this approach is vendor lock-in to webpack. That’s serious, because Rollup is a bit better for bundling libraries. Also, updating to recent webpack versions was always a struggle.

The solution we ended up with is a little bit more extensive, but it uses a Node.js standard resolving mechanism, making it rock solid and future-proof. All we do is add a package.json file to the file structure:

components
└── FilterSlider
├── __tests__
│ └── FilterSlider-test.js
├── FilterSlider.jsx
├── FilterSlider.scss
└── package.json

And within package.json we use the main property to set our entry point to the component, like so:

{
"main": "FilterSlider.jsx"
}

With that addition, we can import a component like this:

import FilterSlider from 'components/FilterSlider'

Component vs PureComponent vs Stateless Functional Component

It is very important for a React developer to know when to use a Component, PureComponent, and a Stateless Functional Component in your code.

First, let’s check out a stateless functional component.

A. Stateless Functional Component

Stateless functional components are a common and concise way to create components without state, refs, or lifecycle methods.

The idea with a stateless functional component is that it is state-less and just a function. So what’s great about this is that you are defining your component as a constant function that returns some data.

In simple words, stateless functional components are just functions that return JSX.

Update: React’s latest version has brought us React hooks, which will let us state, effects and refs in functional components without needing to convert them into class components.

2. PureComponents

Typically, when a new prop is introduced to a component, React will re-render that component. However, there are instances where a component receives new props that haven’t actually altered, but React still initiates a re-render.

Using PureComponent will help you prevent this wasted re-render. For instance, if a prop is a string or boolean and it changes, a PureComponent is going to recognize that, but if a property within an object is changing, a PureComponent is not going to trigger a re-render.

So how will you know when React is triggering an unnecessary re-render? You can check out this amazing React package called Why Did You Update. This package will notify you in the console when a potentially unnecessary re-render occurs.

Once you have recognized an unnecessary re-render, you can use a PureComponent rather than a Component to prevent things from having an unnecessary re-render.

Higher-order Components (HOCs)

At times, it becomes necessary to ensure that a React component is only visible when a user has logged into your application. Initially, your render method will involve several sanity checks until you realize the repetitive nature of your process. As you seek to make your code more DRY (Don’t Repeat Yourself), you will eventually encounter higher-order components that assist in extracting and abstracting specific component concerns. In the context of software development, higher-order components embody the decorator pattern. Essentially, a higher-order component (HOC) is a function that takes a React component as a parameter and returns a different React component. Observe the example below:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
export default function requiresAuth(WrappedComponent) {
class AuthenticatedComponent extends Component {
static propTypes = {
user: PropTypes.object,
dispatch: PropTypes.func.isRequired
};
componentDidMount() {
this._checkAndRedirect();
}
componentDidUpdate() {
this._checkAndRedirect();
}
_checkAndRedirect() {
const { dispatch, user } = this.props;
if (!user) {
dispatch(push('/signin'));
}
}
render() {
return (
<div className="authenticated">
{ this.props.user ? <WrappedComponent {...this.props} /> : null }
</div>
);
}
}
const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
AuthenticatedComponent.displayName = `Authenticated(${wrappedComponentName})`;
const mapStateToProps = (state) => {
return {
user: state.account.user
};
};
return connect(mapStateToProps)(AuthenticatedComponent);
}

The function ‘requiresAuth’ takes a component (WrappedComponent) as an argument, which is then enhanced with the needed functionality. The function contains the ‘AuthenticatedComponent’ class that renders the passed component and integrates the feature of verifying if a user exists, if not, it diverts to the login page. Ultimately, this component is linked to a Redux store and returned. Although Redux is beneficial in this case, it is not indispensable.

ProTip: It’s nice to set the displayName of the component to something like functionality(originalcomponentName) so that, when you have a lot of decorated components, you can easily distinguish between them in the debugger.

Function as Children

The process of designing a collapsible table row isn’t simple. It raises questions like how the collapse button should be rendered or how the children are displayed when the table isn’t collapsed. The advent of JSX 2.0 has simplified this process, as it allows you to return an array instead of a singular tag. However, I will delve deeper into this example to highlight how useful the Function as Children pattern can be. Consider the table below:

import React, { Component } from "react";
export default class Table extends Component {
render() {
return (
<table>
<thead>
<tr>
<th>Just a table</th>
</tr>
</thead>
{this.props.children}
</table>
);
}
}

And a collapsible table body:

import React, { Component } from "react";
export default class CollapsibleTableBody extends Component {
constructor(props) {
super(props);
this.state = { collapsed: false };
}
toggleCollapse = () => {
this.setState({ collapsed: !this.state.collapsed });
};
render() {
return (
<tbody>
{this.props.children(this.state.collapsed, this.toggleCollapse)}
</tbody>
);
}
}

You’d use this component in the following way:

<Table>
<CollapsibleTableBody>
{(collapsed, toggleCollapse) => {
if (collapsed) {
return (
<tr>
<td>
<button onClick={toggleCollapse}>Open</button>
</td>
</tr>
);
} else {
return (
<tr>
<td>
<button onClick={toggleCollapse}>Closed</button>
</td>
<td>CollapsedContent</td>
</tr>
);
}
}}
</CollapsibleTableBody>
</Table>

You simply pass a function as children, which gets called in the component’s renderfunction. You might also have seen this technique referred to as a “render callback” or in special cases, as a “render prop”.

Render Props

The term “render prop” was coined by Michael Jackson, who suggested that the higher-order component pattern could be replaced 100% of the time with a regular component with a “render prop”. The basic idea here is to pass a React component within a callable function as a property and call this function within the render function.

Look at this code, which is trying to generalize how to fetch data from an API:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class Fetch extends Component {
static propTypes = {
render: PropTypes.func.isRequired,
url: PropTypes.string.isRequired,
};
state = {
data: {},
isLoading: false,
};
_fetch = async () => {
const res = await fetch(this.props.url);
const json = await res.json();
this.setState({
data: json,
isLoading: false,
});
}
componentDidMount() {
this.setState({ isLoading: true }, this._fetch);
}
render() {
return this.props.render(this.state);
}
}

As you can see, there’s a property called render, which is a function called during the rendering process. The function called inside it gets the complete state as its parameter, and returns JSX. Now look at the following usage:

<Fetch
url="https://api.github.com/users/imgly/repos"
render={({ data, isLoading }) => (
<div>
<h2>img.ly repos</h2>
{isLoading && <h2>Loading...</h2>}
<ul>
{data.length > 0 && data.map(repo => (
<li key={repo.id}>
{repo.full_name}
</li>
))}
</ul>
</div>
)} />

The parameters ‘data’ and ‘isLoading’ are pulled out from the state object, as you can observe, and these can be employed to direct the JSX response. In this scenario, a “Loading” headline appears as long as the promise remains unfulfilled. The state’s sections that you decide to pass on to the render prop and how you utilize them in your user interface is entirely your choice. In general, this is an extremely potent method to pull out common behavior. The ‘Function as children’ pattern mentioned earlier essentially follows the same pattern, where the property is ‘children’.

Helpful hint: The render prop pattern broadens the Function as children pattern. Therefore, you can have several render props on a single component. For instance, a Table component could have a render prop for the header and another one for the body.

I hope this assists you in utilizing the most effective strategies when building your project using the ReactJs library.

--

--

Abiodun Ajayi

Abiodun Ajayi has more than 6 years of experience in Security and IT architecture. He consults and helps form strategies, perform project feasibility studies.