13
minutes
Mis à jour le
19/11/2020


Share this post

Kickstart your React PWA using these modern React libraries to scale up fast and exploit all of React's potential.

#
CSS
#
Javascript
#
PWA
#
Performance
#
React
#
Typescript
There are many ways to start React projects the wrong way
  • How shall you manage data throughout your app?
  • How shall you kickstart your user interface?
  • How shall you write robust and maintainable code?

 

As React doesn’t natively come with an answer, these are 3 questions you should ask yourself before starting developing your project, because your choices shall impact the maintainability and scalability of your project. Many solutions exist to each of these questions, but only some of them are actually relevant; rarer are the ones that don't come with later drawbacks. Because reworking such structural points is definitely painful, let’s do it right the first time!

There are a few things you need to know about React and the paradigms related to React web development. In this article, I will cover this need and give you tools to apply scalable development reflexes, and why they are the best in their field.

On a rush?

npm install react-helmet react-redux @reduxjs/toolkit redux-saga react-router connected-react-router normalizr recompose @material-ui styled-components react-hook-form --save

npm install prettier @types/react-helmet @types/react-redux @types/react-router @types/styled-components --save-dev

⬇️ Minimal setup

⚛ Install React with create-react-app using the Typescript template

TLDR;
npx create-react-app your-app-name --template typescript

Whatever you are developing, you should never build anything using only vanilla javascript. Even if you find it less intuitive, typing helps you during development by forcing you to cover every case you can face when accessing a value, through an enforced strict and explicit policy. In fewer words, typing - when used strictly - protects you from facing a dumb Cannot read property 'id' of undefined.

Typescript is the recommended choice because it’s now backed up by Microsoft, whereas other typing solutions such as Flow are less and less maintained nor developed, as years go by.

💄 Use Prettier to speed up your development

TLDR;
Install the
Prettier extension on VSCode and run
npm install prettier --save-dev
if you want to run via a CLI

Whether you prefer using VSCode or IntelliJ, Prettier is available as an extension to format your code, following predefined, opinionated rules that you can customize through a shareable configuration file.

Just add a .prettierrc file at the root of your working directory, with these custom rules I recommend:

{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
}
  • singleQuote to enforce a  consistent quoting policy over your entire code base
  • trailingComma to make sure your objects spread over multiple lines all have a trailing comma: this saves you time when you want to add, remove or copy-paste a property of an object
  • printWidth to increase Prettier’s default formatting width, because it doesn’t maximize screen usage on 13' screens

🧢 Use react-helmet to manage your page's head

TLDR;
npm install react-helmet --save

Your React app is a single-page web app. This means that what is sent to your users is a single HTML page and a whole bunch of javascript that populates its body with your app.

This also means that the page’s head is pre-generated and can only be modified when your app is running: that is exactly what react-helmet is made for. One of the main things you’ll want is to change the title of your pages, according to user's navigation:

<Helmet title="My awesome title" />

And maybe wrap it inside a global Helmet, which allows you to set up a title template, such as:

<Helmet titleTemplate="%s - MyApp" defaultTitle="MyApp">
<MyApp />
</Helmet>

Be aware though that react-helmet will change the head according to your will only if javascript is rendered on the client’s side. Usual social media, for example, won’t be affected by the changes from react-helmet: you’ll need to setup server-side rendering of your HTML template to setup your page’s head before it’s sent to the client.

⚠️ If you choose to setup SSR, react-helmet will be useless for you.

📬 Use Redux, Redux-toolkit, Redux-saga and connected-react-router

TLDR;
npm install react-redux @reduxjs/toolkit redux-saga react-router connected-react-router --save
npm install @types/react-redux @types/react-router --save-dev

Whatever app you are building, you will need to access data from your database, asynchronously. But the User Interface needs to access it synchronously from multiple places in your app, and you don’t necessarily want to fetch the data you need in some component every time your component mounts.

Hence, you want to fetch and store data in a place which is called the state. A user should be able to change some of this data through the UI: this is called state management.

Libraries such as Redux and React-recoil have been developed to cover this need. However, Redux is the oldest and most mature framework to build state management on, and therefore should be the preferred choice (coupled with redux-saga, Redux brings many advantages over react-recoil,  see below).

There's a limit to what can Redux do on its own, though: you can't natively execute asynchronous tasks (such as a call to an API) inside one of your reducers, as reducers should not have any side-effect. In order to asynchronously fetch some data from an API, you'll need to use Redux middlewares, such as redux-thunk and redux-sagas. Basically, it allows you to run tasks asynchronously in response to an action.
Redux-saga has many advantages of redux-thunk, such as asynchronous workflows that can be started, paused, resumed or stopped, that you can discover in this article.

However, Redux and especially redux-saga have a steep learning curve: fortunately for us, there are great documentations on their respective website and a great example that teaches us how to implement redux-saga alongside redux. Redux-toolkit helps you implement your redux flow with boilerplate killers.

Once you have your state and your sagas set up, you shall need to redirect the user upon a specific action. To achieve this, you will need to link the browser’s history to some part of your state and create actions/sagas to update it. That’s basically what connected-react-router allows you to do, in the form of simple push actions. See how to set it up and use it on their github.

🔄 Save your state throughout sessions with redux-persist

TLDR;
npm install redux-persist --save

❗️ Indeed, I lied: this one’s optional.

If you are building a Progressive Web App, for example, you may want to save your app's state throughout sessions to allow you user to immediately access data when coming back (instead of waiting for data to be fetched again). To achieve this goal, I highly suggest you use redux-persist, because of its simplicity: you save the state to the browser’s web storage and rehydrate it when coming back on your app.

🎰 Keep your state normalized using normalizr

TLDR;
npm install normalizr --save

Once you are familiar with state management, you will certainly face a common issue: how to store references to data, so that there's no redundancy in your app's state. Let's consider, for example, how a typical blog's state would naturally look like:

{
"posts": [
{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
},
"content": "That is an awesome blog post!"
}
]
}
]
}

Let's now look at how it should look like, to take less storage space and to be able to access any resource without having to know their parents:

{
"posts": {
"123": {
"id": "123",
"author": "1",
"title": "My awesome blog post",
"comments": [
"324"
]
}
},
"users": {
"1": {
"id": "1",
"name": "Paul"
},
"2": {
"id": "2",
"name": "Nicole"
}
},
"comments": {
"324": {
"id": "324",
"commenter": "2",
"content": "That is an awesome blog post!"
}
}
}

To a larger extent, it’s a good practice to move nested data to the common root of their consumers. Don't get me wrong: you can have redundancy (and it's one of the powers of NoSQL), but you should not duplicate whole resources (because you would need larger browser storage and database storage space, which might not please your users). It also means that if you have some nested resource that is only used by its parent, there’s no need to move it.

Normalizr provides you with a way to do such manipulations by defining schemas for your state's data and manage them with 2 functions, as described on their github.

♻️ Compose Higher-Order Components using recompose

TLDR;
npm install recompose --save

At some point in a React app development, you will need to use or implement Higher-Order Components (HOC for short). Recompose is an independent library adding many HOC to React. Most of them are fixing issues developers faced with older versions of React (when props were type-checked at runtime using prop-types and functional components were still stateless), but some of them are still interesting to use.

A common pattern in React UIs, for example, is to display some component if the data it requires is fetched, otherwise a loader is displayed. When you face such a situation, you should wrap up your component with branch and renderComponent HOCs, as such:

import branch from 'recompose/branch';
import renderComponent from 'recompose/renderComponent';
export default branch(
({ isFetching }) => isFetching,
renderComponent(MyLoader)
)(MyComponent);

A typical use case would be to retrieve data from your state and pass it to some component’s props, using react-redux’s connect HOC. Another would be to localize your app using react-intl’s injectIntl HOC. Once you have this need, instead of doing this:

export default injectIntl(connect(mapStateToProps)(MyComponent));

Use compose utility from recompose, so that it is less painful to add a new HOC:

export default compose(
injectIntl,
connect(mapStateToProps),
)(MyComponent);
⚠️ If you only use compose utility function, you better consider creating your own.

💡 You may be wondering why promote HOC in Modern React: the reason is because HOC are not equivalent to Hooks. HOCs are useful to control the rendering of a component. One of the most simple examples of this non-equivalence is React.memo.

💅 Bootstrap your app using Material-UI along with styled-components

TLDR;
npm install @material-ui styled-components --save
npm install @types/styled-components --save-dev

Material Design is without any doubt ruling the world of design at the moment, mainly thanks to one of its key points: accessibility. As you don’t have time to spend hours designing your app and trying to avoid design pitfalls, it’d be great to start with a set of components styled the Material Design way.
Fortunately, that’s what Material-UI provides us with. Plus, Material-UI comes with a handy way to fully customize its components using JSS!

However, if you don’t know what JSS looks like, let me give you a quick reminder:


Simplified JSS for an arbitrary div element, as written with Material-UI’s makeStyles

I definitely wouldn’t allow myself to influence you, but this is certainly less intuitive than what styled-components allows you to do:


The corresponding CSS of the previous example, as used with styled-components

And when you want to build a maintainable project, you definitely care about what’s intuitive for future developers.

📜 Create your forms using react-hook-form

TLDR;
npm install react-hook-form --save

React Hook Forms aims at fixing the main issue you face when using other Form libraries such as Formik: bad rendering performances. It solves it by minimizing renderings, as pretty well covered in this article. No need to re-explain things.

💎 Test your components using enzyme and redux-mock-store

TLDR;
npm install enzyme redux-mock-store --save
npm install @types/enzyme @types/redux-mock-store --save-dev

When talking about maintainability and robustness, tests should come up as one of the top solutions to prevent unexpected behaviors (also known as bugs) from popping in your app.

Fortunately for us, create-react-app is shipped with a testing library for React fully integrated with Jest. Unfortunately, this testing library is not enough to provide us with ways to painlessly write powerful tests. This is where enzyme comes to the rescue: it enables you to easily find and interact with your React elements in the DOM:

const wrapper = shallow(<MyComponent {...myProps} />);

wrapper.find(MySubComponent).simulate('click');

When testing Redux-connected components, you’ll face some issues because you did not provide your element with your Redux store in your tests. redux-mock-store makes it easy to mock the Redux store.

How can I apply all the awesome advice you just gave me?

I created a template repository allowing you to start a React project with all the best tools I presented and some generators to help you develop faster, without losing scalability and robustness.
You are free to star this template repository ✨

This article is the first part of a larger trilogy, which goal is to give you all the best tools, advise you on how to use them and show you all the pitfalls you need to avoid when developing with React. The second part will be released next week 🚀
The second part can be read here.