- Container and Presentational Components
- React Architecture Best Practices
- React Wrapper Component Minimized
- Uniformity Across React Components - Easier to read and adapt
- Testing React Components
- Styling React Components
- Portability of code - Keeping it usable for other platforms
- Making React Tests less annoying
- Keep’em rolling out - Deploy React App
“The real cost of software is maintenance over time because change is inevitable”
These React best practices will help you maintain your large scale React projects more effectively and efficiently.
With the introduction of large scale React projects and complex UIs, front-end engineering is facing new challenges with every passing day. Frontends nowadays comprise thousands of small React components, developed by hundreds of developers, all working together to make our systems work.
While developing large scale systems we often get into these situations where the speed of adding a new feature gradually decreases with the passage of time. These situations damage confidence and relationships between a number of stakeholders associated with that particular product.
Thus starting off or maintaining large scale React applications is never an easy task and requires a lot of attention. Here we will be discussing some of the challenges most development teams face when they are working on large scale React applications and we’ll try to find a way to avoid those pitfalls.
When it comes to better architecture on the frontend one thing that makes a huge difference is an answer to the question “What happens when a change is happening in one module”. As described by Monica Lent in her talk “Architecture as enabling constraints”, restricting how we supply our data through our app, restricting how much abstraction is allowed or restrictions on merging business logic in presentational components. If put together wisely and carefully, these sets of restrictions will then shape a path for your application to grow steadily and smoothly. We will discuss a few constraints that will enable your application to stay safe from becoming a mess when it grows larger.
Container and Presentational Components
The human brain has a cognitive tendency to retain a limited number of things at a given instance. This leads us to divide our programs into smaller units so we can think, code, test and fix one thing at a time. One of the most interesting patterns that came forward in the early days of ReactJS was the container-presentation pattern. The core idea behind this pattern was to keep your stateful or business logic out of your presentational components.
When kept separate from the presentational part, your business logic remains isolated and easier to debug. This will allow your business logic to be used independently across different contexts.
Let’s consider an example of a list of blogs that we want to show on our webpage. Dividing this task into two small things, (1) getting the list of blogs from our servers and (2) showing the data in an optimized list for smooth scrolling and pleasant user experience. Both of these parts require different types of attention and may encounter bugs that are of a very different type. While developing a list of blogs you have to be careful about maintaining scroll speed and making UI similar across different browsers, on the other hand, you have to be sure that your stores get and serve data as per props passed. This stateful logic to hold the blogs list can be used to show blogs on different pages of our application without worrying about how they look on those pages.
Hence the developer writing or fixing the presentational layer has to make sure a11y standards and platform-based optimizations are delivering better aesthetics. Meanwhile, developers writing business logic have to make sure the data that's delivered to the presentational component is derived properly out of the given props. Thus the developers can focus on one thing at a time and changes in one portion will not be making any changes to the other part.
React Architecture Best Practices
As the code base grows into a beast, getting a small thing fixed might eat up a lot of your precious time. As changes occur inside one module, there can be a chance that even a small change might have an impact across many files in your React project that are scattered across your codebase. This will make those changes look ugly and will make it harder to review and analyze the real impact of these changes.
Significant time savings can be achieved if your files and code that belong to each other are kept as close as possible. For example, keeping styles of a component in the same directory of its JSX will make it easier for a developer to see why a certain component isn't showing up as expected on mobile devices.
It’s not just about keeping styles, keeping actions, reducers and side-effects and API calls beside your business logic component/hook will make it easier to debug an issue that belongs to a certain module.
React Wrapper Component Minimized
While working with large scale React apps we usually use a lot of third-party libraries that help us move faster. As time passes we might get into a situation where that third-party library may not serve our purpose or we have a better alternative to that library, but it has a different API. In such cases, our decision to move to any other library may hit our codebase quite hard if that library is not used wisely.
It is always good to wrap your own API around any such libraries and use them via wrapper across your app. Let's say you’re using a library for displaying an image slider in different components of your app. One way to do this is to directly import it into the components it has used. This approach will make it difficult to replace this library for a better one when required. Any such change will impact a number of components that are using this library. A React best practice would be to wrap this library inside a component and use your component throughout the app. This will allow you to move from one package to another without hitting your codebase a lot.
Beware that too much abstraction may make your code uglier, be very careful and wise while introducing any such abstraction.
Uniformity Across React Components - Easier to read and adapt
One of the most important scalability factors is to have a system that supports multiple developers working together on one or more features simultaneously. In addition, the ability for new developers to join the project without having to go through tons of code is a necessity.
In our applications, developers create new components every now and then while working on them and most of these components in a number of cases may import certain libraries or wrappers for styling and showing text.
For example, all presentational components might use a file associated with them containing all of their styled components and also react-intl’s formatted messages to show text. We can automate the creation of a new component with a directory that contains a file “Styled.js” & “messages.js” along with “index.js” that imports both of these files besides React itself and other libraries. You can also include files with the basic code for writing unit tests for that component.
This will allow developers to create a components directory that looks quite similar to other component directories. This also helps other developers work on that component later to relate things quickly.
You can use Plop JS to write such generators
Testing React Components
Today steady and fast wins the race and that is what we always strive for. Getting to know about basic issues in your code after executing it might be costly for us today. JS apps at times break due to a Just-In-Time compilation of code, (un)fortunately there isn’t any truly advanced IDE for JS developers like Android Studio or Xcode to see those errors beforehand. In order to keep up with the focus and pace of developers, we have to make sure that we spot the issues as the developers are writing their code.
Linting your code can help you avoid problems that may occur once your code is executed. Thanks to Eslint, developing JS apps is getting much faster with less hassle. A good set of linting rules will allow your developers to save time as they don’t have to spot errors by executing code.
Styling React Components
As the number of developers working on an app grows we see a lot of different styles of coding start coming into our codebase. Some people prefer tabs over spaces, some people avoid curly braces around a single line of conditional code while others see it as a crime, few people will care about the beauty of the code while others will not unless mentioned in the PR review.
As we discussed earlier in the uniformity section, handling a number of incoming and outgoing developers in a long term React project is very crucial to its success. Enforcing some of the beautification rules in apps will make it easier for developers to read.
“Any fool can write code that a computer can understand. Good programmers write code that humans can understand”. — Martin Fowler
Few tools can be very helpful in this regard:
Portability of code - Keeping it usable for other platforms
React JS has one of the most attractive taglines “Learn once, write everywhere”. This was one of the major reasons for selecting ReactJS over other frameworks at Peekaboo Guru & we don’t have any regrets to date - Celebrating the 3rd anniversary of our product. With as much effort as Facebook has been putting in React-Native, it's now easier to leverage not only your learning but also your code base across platforms. One of the challenges is to keep your codebase in a form that can be portable across multiple platforms.
Keeping our blog listing examples in mind, our store-connected React hooks will remain the same for our web and mobile apps that were developed using React-Native while the presentational parts will be updated according to the platform. This allows teams to ship features faster across all platforms. Also, managing your content with a headless CMS like ButterCMS also helps keep your project simple to manage since all the portable content is simply fetched via API.
Making React Tests less annoying
Keep’em rolling out - Deploy React App
The bulk of feature requests and a number of bug fixes are done by teams working on large scale React projects. This may result in new bugs that teams may not anticipate before they happen. This shakes the team’s confidence when they are ready to ship their new code. With consistency in the integration process in place, teams are more likely to commit code changes more frequently, which leads to better collaboration and software quality.
With the selection of good CI/CD tools, teams can be more confident & paced up while shipping new releases.
Writing & maintaining large scale React projects is never a piece of cake, rather it’s a long marathon that requires a consistent focus on React best practices. In order to maintain high standards of quality and keep the delivery pace in the longer run, we need a solid foundation in place. If laid down properly, this foundation can help you a lot as your application grows bigger. As discussed, the path to nirvana starts with isolation of concern, enabling constraints in your application’s code. Moving forward one has to keep up with the pace of developments done on delivery platforms to cope with the high standards being set in the industry.
Learn to build a serverless React application in our new blog post on how to use Serverless in practice.