7 Steps to Modernize and Optimize Your React App
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.
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.
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.
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.
You can see the improvements that are almost 100% in the graphs. In addition, webpack 5 prepares the foundation for the more major improvements.
This version upgrade may not be that challenging, even this can be a good opportunity to upgrade all the libraries to their latest versions.
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.
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.
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 theconnect
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.
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