React is a JavaScript library. Its reusable components accelerate application development and ensure high performance from the React app itself. It also allows for automatic testing, reducing the number of bugs and decreasing the overall costs of maintaining an application. Following best practices and using battle-proven tools will unlock the full capability of React in web development. Our experience as React developers is summed up in this blog post on React best practices.
#1 Use functional components
Components in React can be defined as class-based or functional. Over the life of this library, the norm has shifted from the former to the latter.
Making this shift comes with important advantages in terms of the amount of concise, readable code you will write and how easy it is to read, compared to classes. You can take the fact that the official React documentation is being rewritten using functional components as a sign.
Most of the actively maintained resources available for React make use of functional components. Practically speaking, this means that if you need community help when you run into problems, the guides and resources will be easier for you to understand and follow if your components are likewise functional.
Moreover, functional components are easier to test, and due to the way they are implemented in the React runtime, they simply perform better. If a given component can be defined as a pure function that takes props and returns JSX, functional is the way to go.
#2 Use hooks
React 16.8 introduced the hooks API, letting developers avoid the weight of class-based components in favor of functional components for all of an app’s UI logic. Some of React’s functions are best accessed through hooks, especially interacting with the state of a component in order to run an after-effect depending on how its state changes. No writing code for class components is necessary.
Better yet, React hooks are reusable, so the logic only needs to be typed out once. Without them, using class-based components instead, sharing logic between components meant either putting that logic into a higher-order component (HOC) or lifting the state up to a common ancestor.
So, the hooks API lets you define custom hooks that encapsulate logic for reuse across multiple components. These custom hooks can be ported to any point the app requires them, reducing duplicate code, and there is no limit to the types of hooks that React allows developers to create.
#3 Use small components
The size of the components matters. Smaller components are easier to understand and use, which allows you to onboard new people to the project much faster. It is also easier to test them, which shortens the time needed to write tests and decreases the number of bugs.
While building an app, it is good to choose a maximum size a component may be. It is easier to apply the principle of the Separation of Concerns (SoC), i.e., separating an application into distinct modules addressing different functionalities, in small components. It is also easier to follow industry guidelines like WCAG and to optimize web pages for SEO.
If your component reaches your size limit, consider how code splitting can help create smaller, more specialized parts. Components should also be divided according to their functionalities or the layer in which they are used, such as the presentation layer, business logic layer, data access layer, persistence layer, etc. If there are problems, you can use an advanced React technique called higher-order component (HOC) that allows you to reuse component logic.
Keeping each React component small and function-specific is also very important from a business perspective because it considerably reduces the costs of application maintenance.
Read more on Web development:
Headless CMS vs. traditional CMS—comparison
A web application security checklist for every stage of development
Micro frontends: pros and cons
Progressive web apps (PWA)—a gentle introduction
Angular vs. React—which one to choose
React vs React native—which one is better for your business
#4 Keep application business logic and UI layers separate
The HOC technique also allows you to separate application layers, in particular separating business logic from the UI logic. To do so, we follow the DRY principle (Do not Repeat Yourself), as opposed to reusing the same app logic, even if it’s duplicate code, in a number of different components. When components include both kinds of logic, your logic trees are too complicated. The components are less reusable, more difficult to test, and difficult to refactor, especially when the purpose is state management.
This principle can help you in multiple ways. The entire modification process is faster because you can find the logic you want to change—either the app’s business logic or its look and feel—more easily in a smaller component. There are also fewer app components because you can reuse the smaller items many more times throughout the app. This works for elements such as a button or menu, or entire forms and functionality logic, which can significantly simplify the maintenance process. Adding a new element to your React project is likewise easier, involving the addition of a single component rather than editing a number of existing components.
To get more technical, the logic for placement of a state should be written into a hook of its own, rather than writing the logic inside a component. Where logic is needed, a child component can be called inside while a parent component hands over any props that will be needed.
Keeping business logic and UI layers separated brings an additional benefit. UI components built this way can be presented easily, in the form of a component set (e.g., using Storybook) or in the form of a library. It then becomes available for discussion with the UI/UX team or for manual testing in a secure environment.
Finally, such a library makes it quicker to introduce a new team member to the project.
#5 Have a clear distinction between reusable and business-specific components.
At the application level, it is important to separate reusable components from those responsible for business logic. Reusable components are used to build low-level interactions and should be reused as often as possible. Components responsible for business logic join together smaller components to build functionalities that execute operations required from the business point of view.
A clear distinction between these types of components will allow you to decide whether you need a new component or not. And this drastically reduces the maintenance and modification costs of a web application.
These last five items can be summed up thus:
- If you have long conditional statements, separate the individual variables.
- Give each UI element its own tree.
#6 Use TypeScript
JavaScript is a dynamically typed language, which makes it very flexible. However, in React, components do not have strict data types, which makes it more error-prone and makes it more difficult to build new functionalities or maintain existing ones.
Therefore, TypeScript is an ideal addition to React applications. It can be used in many places, from supporting application API, through app logic, to the smallest UI components. TypeScript ensures the integrity of data types in the entire web application and allows you to avoid type mismatch errors. Additionally, it speeds up the process of implementing modifications in already existing components, highlighting places where type modification is required.
With TypeScript, you can easily use app elements that already exist, supporting the IDE (Integrated Development Environment) with additional information. This makes a web app more reliable when implementing new functions and reduces maintenance costs and the time needed to implement new features.
PropTypes is another popular option for type checking. It is not as powerful as TypeScript, but it requires less setup and configuration. Regardless of the choice you make, your React application will be more reliable and scalable if you have some method for type checking.
#7 Create a common data model for the frontend and backend
Whenever you implement new functions or modify existing ones, ensuring consistency between an API and an application is responsible for the major portion of your time and labor cost. OpenAPI and an automatically generated API interface are very helpful in ensuring the frontend and backend of the application are integrated, without involving developers. When used together with TypeScript, OpenAPI ensures consistency across the application and significantly decreases the cost of API service maintenance.
When building a new React project, developers have access to ready-to-use methods and do not have to delve into low-level details of API calls. In the case of more complex functionalities, object-oriented TypeScript and ready-to-use data models from OpenAPI allow you to build intermediary models that make it easier to format data, serialize and deserialize it, or use programming patterns according to the current needs. Such an approach separates the data layer in the application and streamlines the management of the React components.
#8 Use proper communication methods between components
In React, components are organized in a component tree with child and parent components. They communicate with each other in many different ways. The basic communication method is props/callback and the more complex one is React Context, while Redux is the most advanced method. In large applications, most frequently a combination of all three methods is used. Where a given method is used is also important. If a method is applied incorrectly, it may increase the costs of your React project.
“Props” are a set of variables used to pass information down the component tree, from a parent component to a child component. “Callback” functions, on the other hand, are used to propagate information in the opposite direction. “Callback” functions themselves can also be passed down as props from parent to child components. The props/callback method is used to ensure direct communication between parent and child components and to perform simple functions.
Bear in mind that communication between components in your application should not be too complex. If there are many components embedded into each other that send data back and forth while communicating, it is difficult to track such data flows. Simpler communication means that you can detect possible threats faster.
React Context is used for communication between components belonging to the same branch in the component tree. Make sure that this branch is not too complex and monitor the performance of such a solution. This method should not be used to build more than one functionality.
The Redux method is used when you need to ensure communication between different components across different levels of the application. It can be used for many different functionalities and dependencies between them. You need to monitor the number of Redux elements called reducers and split them into smaller, independent groups, if needed.
In Redux, the communication between functionalities should not go through UI elements. It is the user who should be the source of an action and not a component. Also, the HOC technique is very useful when using the Redux communication method.
#9 Use JSS to manage CSS
Styles and themes are an important but complex task that is integral to building and maintaining apps. However, large CSS files just add to the complexity, and having different stylesheet files for each and every component would end up creating a hefty file structure that’s difficult to navigate.
Using JSS to manage CSS may not be a very elegant solution, but it is a very practical one and works best in most scenarios. It allows you to define reusable styles and generate dynamic styles on the fly.
A wide range of ready-to-use library solutions are already implemented in JSS. Material UI is one of the most popular ones. It allows you to use many tested and ready-to-use React components and speed up the implementation of simple views. In the case of more advanced views, Material UI offers you off-the-shelf solutions that can be used as a basis for building your own themes or even components and can apply styles to them.
Since JSS is modular, you can limit the number of CSS styles on the web page to the ones that are requested. This decreases the amount of data transferred. A standard or custom theme makes the work easier, ensuring that frequently used elements are available when applying the styles to the React application.
#10 Use server-side rendering (SSR) while serving your React application
When it comes to serving an application, a React web app can be a part of a bigger service and be used only in some of the service’s parts. Alternatively, it can be one small component embedded in a service or be served as a client-side rendered web page.
But most frequently, a web app is served as a multi-functional information service. In that case, there can be a problem with startup times. To solve this, we need to start fetching from the server as soon as possible. A venerable solution is server-side rendering (SSR), which forces the rendering to begin before being sent to the user. Unfortunately, that can double the computational work if there is rendering to be redone on the user’s computer (especially if a state changes), or slower reaction times if a state change has to be rendered on the server before the change is reflected on the user’s computer.
Instead, streaming dynamic pages will cause the browser to start downloading all of the assets in parallel. Two available tools are Next and Gatsby. These libraries offer nested routes, providing full, dynamic control over the SSR.
The differences in the way they work means you have a choice to make. If you need to ensure communication between the server and API, choose Next. If you need high performance, Gatsby is the preferable option.
#11 Test all your React components
It can sometimes seem that linting and testing slow down your work and indeed many developers neglect to do it. When it comes to the implementation of any programming language, adequate testing is needed. In fact, there are advantages that these developers are missing out on:
- Testing does not just turn up errors. It also allows you to check that each snippet and component acts as you expect and intended.
- The goal is to ensure that the new code added to your project integrates with the existing code without breaking existing functionality.
- Finding errors and bugs early means the final product is of greater quality and is easier to maintain.
- Tests can produce useful documentation for progress reports and future reference.
- It will cost much more time and money later if an unfound bug causes a serious problem after rollout.
- Users are much more likely to trust and give their loyalty to bug and error-free apps and websites, which leads to better growth prospects.
With so many tools easily available to test React components, a React developer is best advised to test as they go. This is true even if they import React code. There are plenty of tests to choose from for component function and for browser rendering tests, and to a large extent the choice of tool depends on which works best for you.
Beyond the React Test Library, there is Jest, a JavaScript test runner that emulates the HTML DOM with jsdom.
You can also run your React apps in your browser as you build them. Keep in mind that this kind of test is not completely accurate in a browser that is fairly new to the device that you are working on. Otherwise, an error that appears could be attributable not to your code but to the code of the ported browser.
#12 Pay close attention to new React versions
Since the benefit of being open source is the number of minds constantly finding new ways to improve the code, there will always be changes to be aware of. There will be new functions and there will be modifications to old functions, if they are not deprecated altogether.
If you consistently monitor and stay up-to-date on each new version of React, you will be ready to optimize or make changes to your code base when necessary, or when desirable for better performance.
Checking the official documentation is the best way to watch for changes, since it comes from React creators. Join React communities on social media, too, because this is where fellow developers spread the news about and discuss the ramifications of any changes.
Admittedly, there is a lot to keep up with. Each external library might also be given an update; sometimes, a change in React itself has the potential to force changes to an external library. Of particular note is React Router.
Final thoughts
Following these React best practices, you will be able to build both simple single web pages and complex web services with thousands of pages, as well as propose a solution that would suit an information web page and CMS. They will also help you optimize the costs of building new functionalities and maintaining existing ones. Last but not least, you will work smarter, not harder.
This article was originally published September 23, 2021, and updated August 10, 2023.