7 Steps to Modernize and Optimize Your React App

Mucahit Gurbuz
JavaScript in Plain English
8 min readMay 15, 2021

--

Photo by Ferenc Almasi on Unsplash

As the nature of the frontend environment and the community, tools and the tech stacks are transforming so fast. These transformations are aimed towards better developer and end-user experience in general. Mindsets are improved as a result of the challenges faced by the developer day-to-day. These experiences lead to advancement in approaches with highly engaged communities, resulting in apps that are better in scalability, performance, and from an adaptability point of view.

With this speed of change, not every app succeeded to maintain its currency. Even if it is your app or the app of the company that you just started working on, you will need to refactor the app to have more modern approaches and strategies.

My team and I at OPLOG are in this modernization process right now. The app’s codebase was established in 2018 and for some reason, the major structure has not changed since. We aim to adapt our apps to recent developments in the frontend world and increase the developer and the end-user experience. To whom it may concern, our tech stacks are TypeScript, ReactJS, Redux, Jest-Enzyme, Styled System (CSS-in-Props), Atomic Design.

Below, I am sharing the steps that we followed with the reasons and incomprehensive impacts that we experienced.

1) Convert classful components to functional components

As React 16.8 we have met the React Hooks. Before then, to use life-cycle methods we were using classful components. With the help of hooks, we can have all the capabilities that class components have for the functional ones.

Photo by Ferenc Almasi on Unsplash

As the nature of the basic input-output mechanism and the power of the hooks, the react community suggests using functional components in every scale app. In addition, most of the popular libraries established custom hooks to get rid of props injection with the HOC’s.

With the help of the easiness in the transformation of a classful component to a functional component, we have converted all our components in our app in a very short time. We formed a basis for the next steps in modernizing our app by doing this.

2) Redesign file structure and break up components into smaller pieces

The file structure is one of the most crucial points when we talking about scalability and maintainability.

We are using Brad Frost’s Atomic Design Methodology for our component structure. We have atoms, molecules, organisms, pages, and templates. You can read Brad Frost’s blog page to have more ideas about the Atomic Design Methodology.

Atomic Design Methodology (Taken from https://bradfrost.com/blog/post/extending-atomic-design/)
Atomic Design Methodology by Brad Frost

As a team, we like this methodology a lot. It feels very organized and it is very easy to determine which components go where. With the coding in the WMS (Warehouse Management System) domain for almost two years, it also proved itself with scalability. Finding the code-block that you are searching for and manipulating it are very comfortable.

Atomic Design Methodology with Bones

Despite these positive opinions, there was a problem with the component sizes. Related to the business logic, or design requirements, the lines of components’ code could increase more than 1000.

To solve this issue, we added another folder to the system which we called “bones”. We have limited components’ length with the 300 lines of codes. If the component needs to have more than 300 lines of code, we are dividing them into bones which are the smaller chunks of the main components where they have their own logic. These components specifically belong to the main components that are divided into bones. They should not be used in any other components.

You can see the image above to see how we divided the OrderDetails component into bones. We have also placed our “tests” folder which includes spec files in the parallel of our components.

We realized that 300 is a good psychological limit to have more clean and easy-to-read atomic components.

3) Use Webpack 5

With Webpack 5, we got lots of fancy and significant improvements in webpack and webpack-dev-server. We also moved one step further to micro-frontend infrastructure (see Micro frontends with Webpack Module Federation).

It was kind of a challenging task to transform the detailed webpack 4 configurations to webpack 5. There are lots of breaking changes. Luckily, the webpack team shared a migration guide to follow. After meticulous work, we upgraded our webpack version to 5 and webpack-dev-server to 4.

I have shared my experiences in detail while upgrading the webpack to v5 in a separate blog post. The blog is in Turkish, so the Turkish speaker can read it and apply it to their projects. For the others you can check the following links to have a better idea about webpack 5 and migration to it:

https://webpack.js.org/blog/2020-10-10-webpack-5-release/
https://webpack.js.org/migrate/5/
https://frontend-digest.com/whats-new-in-webpack-5-ef619bb74fae

I will share some results from our app that includes changes in key performance indicators. In addition to indicators, there is a significant improvement in end-user and developer experience.

Webpack 4.* Lighthouse Values

You can see the improvements that are almost 100% in the graphs. In addition, webpack 5 prepares the foundation for the more major improvements.

Webpack 5.* Lighthouse Values

This version upgrade may not be that challenging, even this can be a good opportunity to upgrade all the libraries to their latest versions.

The change in the performance indicators between webpack v4 and v5

4) Upgrade React and TypeScript to the latest versions

It is always nice to have the latest major version for the major libraries. Although React 17 and TypeScript 4 have no breaking changes, they came up with some nice features and peer dependency compatibility with the other recently upgraded libraries.

We have upgraded our React(to v17) and TypeScript(to v4) libraries during this modernization process.

Upgrading the TypeScript version was an easy task. There was no conflict or break in the codebase. However, during the upgrade of React, there were some peer dependency conflicts that appeared with some libraries that have outdated versions.

To solve these conflicts some major version upgrades had to be made to those libraries which leads to some breaking changes on the codebase. As I mentioned earlier, these are good opportunities to upgrade your dependencies to recent versions.

As it is not in the scope of this article, you can find out the changes obtained after these upgrades in the following blog posts:

React 17: https://betterprogramming.pub/whats-new-in-react-v17-68b7e15576e1
TypeScript 4: https://javascript.plainenglish.io/features-and-breaking-changes-in-typescript-4-111e6551cd7

Especially, the instant hot-reload feature and the removed necessity of importing react library in the components are two major improvements that we like in React 17.

5) Upgrade react-router and use its new hook-based functions to navigate

We were using withRouter HOC to inject router props to our component and read the router states from the component’s props. As the react-router v5.1, we can now use hooks to reach router states and navigator functions inside the components.

This helps us to get rid of the unnecessary complexity of injected props and HOC functions that need to wrap the component.

useHistory , useLocation and useParams are the most popular hooks.

Example usage of the react-router hooks

The useHistory hook gives you access to the history instance that you may use to navigate. The useLocation hook returns the location object that represents the current URL. useParams returns an object of key/value pairs of URL parameters.

6) Replace or upgrade your i18n library to use a hook-based translation function

The i18n library we were using was react-intl . We need to upgrade two major versions of it to have a custom hook feature to reach intl state inside the component. There were so many breaking changes that need to be configured. Therefore, although it has enough capabilities, with respect to the easiness of the use and the community support, we decided to migrate our i18n library to i18next and react-i18next.

The migration process was very smooth. We have used exactly the same translation files in the i18next and configured to our needs.

In order to be freed from injectIntl HOC to have modified every component in the app. We have removed injectIntl HOCs and used useTranslation() hook to reach translation function.

Before (left) and after (right) of usage of the i18n libraries to translation

We also modified our unit tests as there are breaking changes. However, it is much easier to write a unit test for components that have hooks instead of HOC to wrap them.

7) Use hooks for Redux dispatchers and selectors

I have always tended to forget the structure of mapStateToProps , mapDispatchToProps and the connect HOC to wrap component. If you are working with TypeScript it can be much messier where you always need to copy from other components. Besides, there is clearly a redundant complexity when we look at the thing we want.

With the react-redux v7.1.0, useSelector and useDispatch hooks were joined our lives. With the help of these, we were freed from writing 2 separate functions (mapStateToProps and mapDispatchToProps) and a wrapper HOC to inject redux props to the component. These hooks are enough to subscribe to the Redux store and dispatch actions.

As react-redux docs say;

“We recommend using the React-Redux hooks API as the default approach in your React components.

The existing connect API still works and will continue to be supported, but the hooks API is simpler and works better with TypeScript.”

You can find out more information about using hooks in a React Redux App in the documentation.

This change made a significant impact on the developer experience in a positive way. We have removed all the container components where we use them just for the redux, translation, and router wrappers. It is now simpler to deal with TypeScript and reach the required states inside the components.

Before (left) and after (right) of usage of the redux hooks to subscribe to the Redux store and dispatch actions

Above you can see the impact of this change clearly.

Even there is more to do for optimization and modernization of an existing React App, this is a very good start. We as a team experienced a huge difference in both developer and end-user experiences. It is much easier and faster to write a component and it takes less time to land on a codebase.

The below two gists have the same output. You can see how much shortened the component is.

We have come up with these steps after years of experience on sustainability and scalability issues. React apps that are under development should always be up to date with the recent developments.

It is very crucial to be in a mood of continuous development, especially for the frontend world.

I would like to thanks my teammates Emre Keskin and Oğulcan Davran to inspire us with their high level of vision and experiences.

Stay connected!

More content at plainenglish.io

--

--

Software Engineer at Babbel working mostly with React with TypeScript — Cutting Edges