React Architecture | Best Practices

Suneet Bansal
8 min readMay 28, 2023

Now a days, ReactJs is widely used View library worldwide. So it becomes very important to apply best architecture practices to make code reusable, maintainable and also it would help to improve code readability.

However, as React only takes care of the view layer of an application, it doesn’t enforce any specific architecture (such as MVC or MVVM). This can make it difficult to keep your code base organized as your React project grows.

In this article, you will learn what best architecture practices you should apply to the code while using ReactJS to create 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 one of the most common types of components in your arsenal. They provide us with a nice and concise way to create components that are not using any kind of 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

Usually, when a component gets a new prop into it, React will re-render that component. But sometimes, a component gets new props that haven’t really changed, but React will still trigger 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)

Sometimes you have to ensure that a React component is only displayed when a user has logged into your application. Initially, you’ll do some sanity checks in your render method until you discover that you’re repeating yourself a lot. On your mission to DRY up that code, you’ll sooner or later find higher-order components that help you to extract and abstract certain concerns of a component. In terms of software development, higher-order components are a form of the decorator pattern. A higher-order component (HOC) is basically a function that receives a React component as parameter and returns another React component. Take a look at the following example:

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 requiresAuth function receives a component (WrappedComponent) as a parameter, which will be decorated with the desired functionality. Inside that function, the AuthenticatedComponent class renders that component and adds the functionality to check if a user is present, otherwise redirecting to the sign-in page. Finally, this component is connected to a Redux store and returned. Redux is helpful, in this example, but not absolutely necessary.

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

Creating a collapsible table row is not a very straightforward task. How do you render the collapse button? How will we display the children when the table is not collapsed? I know that with JSX 2.0 things have become much easier, as you can return an array instead of a single tag, but I’ll expand on this example, as it illustrates a good use case for the Function as children pattern. Imagine the following table:

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>
)} />

As you can see, the data and isLoading parameters are destructured from the state object and can be used to drive the response of the JSX. In this case, as long as the promise hasn’t been fulfilled, a “Loading” headline is shown. It’s up to you which parts of the state you pass to the render prop and how you use them in your user interface. Overall, it’s a very powerful mechanism to extract common behavior. The Function as children pattern described above is basically the same pattern where the property is children.

Protip: Since the render prop pattern is a generalization of the Function as childrenpattern, there’s nothing to stop you from having multiple render props on one component. For example, a Table component could get a render prop for the header and then another one for the body.

I hope this would help you to apply best approach while using ReactJs library to build the project.

Stay tuned and subscribe to my Medium Channel!!!

Thanks!!!

--

--