Four Tips to Write Maintainable React Components
In short
This article discusses common mistakes in React development and offers solutions:
- Minimize State: Avoid unnecessary state; derive from props and state as much as possible.
- Use Component State: Know when to use Redux; local component state can be simpler for certain scenarios.
- Avoid Refs: Refs break encapsulation; consider using props instead of refs for better maintainability.
- Avoid Imperative APIs: Embrace React's declarative nature; minimize the use of imperative APIs for cleaner code.
React is a declarative UI framework
It allows us to declare how our components should look, and the library manages everything for us. This is great, because it makes maintenance and refactoring components easy. However, sometimes it’s easy to make mistakes which makes it harder to maintain and debug components.
I’ll discuss some of the simple mistakes that I’ve seen people do, and how we can avoid them.
1. Minimize state
I’m not encouraging to use the eslint plugin which forbids setState. State can actually be very useful. But the problem is unnecessary use of state.
Consider the following example, a simple input with autocomplete, which receives all the data as a prop and filters it to get suggestions as you type:
Note: The onChangeText handler is purely hypothetical :)
This looks pretty innocent. But we already have a bug here. If this.props.data updates, the suggestions won’t update. We can do the following so that suggestions update when the data updates.
Agreed, this is a very trivial example, and the bug might not even matter for a simple autocomplete. This becomes visible when as components become more complex and have more logic. While it’s easy to fix, we could’ve avoided this issue altogether. The simple alternative is to calculate suggestions in render.
Notice how our component becomes smaller and the bug is gone without having to do anything special. The trick is to minimize unnecessary state and move as much logic you can to the render function. In short, always derive from props and state as much as you can.
2. Use the component state
Redux is great, and makes managing complex apps much easier. Though it’s important to know when you need to use Redux and when you don’t.
Consider the following example, a simple form with some data. We store the form state in Redux. So we need a Form.js (presentational component), FormContainer.js (container component which connects the form to redux store), form.js (reducer for updating the form in redux store).
I’ll not write down code for all the files, but will focus on the presentational component.
Our component looks nice and simple, but we already had to create 3 files for this (we didn’t even make an action creators file). And there is a hidden bug.
Say the user navigates away from the form. And then comes back to the form later. The form already has the inputs filled with what he had entered previously, which might not be desirable.
To fix it, we’ve got to do the following:
But wait, this still doesn’t fix the issue if we’re actually persisting the redux state to localStorage. We could add logic to ignore the form where we persist the data, but the simple alternative is to not use Redux at all.
Our component’s state is local to the component and we don’t need to do extra work to clear the form when component is unmounted. Sure, we’ve to write a little more inside the component, but it makes up for not having to create all those extra actions and reducer.
Agreed, sometimes it’s desirable to keep the state persisted, but not always. And when it’s desirable, it’s not a lot of work to move the state to Redux.
3. Avoid using refs
Refs are a way to access a property or a method on component instance. They can be useful in some circumstances like focusing an input. But using refs in your own components can make it harder to refactor things.
Refs break encapsulation by exposing component’s internal properties, and it’s difficult to refactor a component without having to search everywhere for what properties are used. In addition, due to the imperative API, most of the time you need to manage things manually, which is always a source of bugs.
Consider the following example, we’ve a component which displays a banner when there are no online users.
Now this looks okay, but we already have a bug. When number of online users change, we don’t hide the banner. Now that’s easy to fix, we can handle it in componentDidUpdate.
Ok, now we have extra code to manually show and hide the banner. And it’ll just get more complex as our conditions change, say we want to show the banner only when there are less than two online users, and none of them in our friend list. You can figure out the code, but it could have been much simpler.
Imagine that our <Banner /> had a prop called visible. Now we could just use it declaratively rather than having to manage it all by ourselves.
Our code becomes much simpler and there’s no manual work. React will handle it for us.
4. Avoid imperative APIs
One of the things I love about React that it’s declarative. But it takes time get used to do things the react way. Recently, I was refactoring some code in one of our apps and unintentionally broke a totally different component.
One of the two components was rendering buttons in a toolbar.
The other component was a password form.
There are two things here, first, we’re trying to make the details route protected, by showing the password screen and then manually navigating there when password is allowed.
But it’s not really protected, since any other component can just navigate to details without navigating to the password screen.
Let’s modify our details screen component to actually make it protected.
Here, we can store the allowed flag in the Redux store or in a parent component state, whichever makes more sense for the scenario.
Second, we manually update props of the toolbar. So the toolbar component is indirectly dependent on the PasswordForm component. If we forget to do this, then our toolbar will never update. We can use the allowed flag from our Redux store instead.
Here we simplified our component by doing less number of manual things and fixed a bug too.
When I started with React, I made many of the above mistakes. It takes a bit of time to get used the React way of doing things. In the end, it’s worth it. I hope this article helped you with writing better code.