Wouldn’t it be great if all of the code we work with was written in a way that it’s completely clear what it does? And that we could easily make changes without breaking it? Sounds good, but it’s not that easy to do so. To get to that level we need to change our mindset a bit.
React apps grow very fast. The project gets more and more components, the codebase grows, and just when you think you are done with a component and forget about it, there are change requirements for it. You analyse the code of that component, try to understand what the author had in mind, even if the author is you, and you stumble upon a condition that you have no idea why the author put it there. You start trying to understand why that condition is there and which use-case can trigger that path and that takes some time. Could all of that be escaped? Yes, at least partially. As a React developer, with or without experience we encounter these situations on a daily basis. What can we do to improve code quality and make our components reusable and maintainable?
You can improve naming by analysing how the libraries you use named and designed the API you are consuming. Sometimes we tend to use names that are too descriptive, and you (most likely) won’t see such cases in the libraries you use. When naming functions or variables I ask myself these questions:
Keeping names simple and intuitive makes the code more readable. Easy to read code is easier to understand and thus easier to maintain.
filterWhenTimeUpdates() → filter()
When a time update is an event that we will respond to by calling the filter function. In code it could be something like this:
useEffect(filter, [time]); Which makes it clear that we will trigger the filter whenever the time is updated
shouldFetchNewData → shouldFetch
You most likely won’t have a case where you would fetch old data
hourOfDay → hour
We would automatically put an hour in the context of the day
allComments → comments
It is used with the same meaning in mind as just comments. Usually, when the arrays are filtered, we don’t mutate the array.
rows, → rows,
}); }, [columns, spacing]);
We can borrow the idea from built-in React hooks and design our hooks in the same way. We can pass a list of dependencies to our custom hook as a separate parameter. This way, in one component we can have this hook triggered onMount only, while in the other component it can be triggered whenever column or rows data changes. Since we are passing the list of dependencies as a separate parameter the same way that built-in hooks do, it will be intuitive to React developers what our intention was.
Don’t put (a lot of) logic inside of the JSX
The Component will be easier to maintain if the JSX part or the Presentational part of the component contains as little logic as possible. If we had the need to refactor or modify the component for some reason, we could do it a lot faster if most of the logic comes from non-JSX parts of the app.
Reuse Selector pattern idea
If you have worked with Redux you’ve probably heard of the Selector pattern. This pattern lowers the amount of effort we have to put in when the data structure changes. A selector is a simple function that receives some data and returns only a (selected) piece of that data.
Data structures tend to change in the early days of development. When that happens, if we are using the selector instead of accessing the data directly in our components, we only have to make a single change. That change would be inside of the selector. If we didn’t use the selector we would have to make changes at each place the data was directly accessed.
What if we were to do something similar everywhere in our components?
If we don’t depend on the data structure or the source where that data came from, every change that occurs will be easy to implement. The goal is having to make changes in a single place only.
How can we achieve this?
We could write selectors and/or use object and array destructuring. Note that this takes up more memory, but the code becomes easier to maintain.
Comment your code
You probably read that comments are bad and that code should be self-documenting. My opinion is that code can’t say everything. I have been in so many situations where I had no idea WHY the programmer wrote some piece of code. Not to be confused with WHAT the code does because that we can read and understand. What we cannot know is which use-cases the developer had in mind when the code was written. Maybe we will break something if we modify that code. There could be some business rules that cannot be explained with code or at least the person who wrote the code didn’t manage to do so. If the author of the code had left comments on why that piece of code is there, it would have saved our time. The problem with comments is that they usually aren’t maintained. People modify the code and not the comment. So the comment ends up having false statements. Thus, maintaining comments would be another tip. A stale comment could be worse than no comment if it misleads you.
When the component has more than a couple of hundred lines of code it gets harder to read (I prefer to keep it under 300 lines of code). More often than it happens in smaller components, the order of defining things gets easily messed up. It’s easier to maintain logical units when the component is fairly small. From my experience the bigger the component gets, the messier the code will become.
How can you ensure that your components stay small? By extracting! You can extract utility functions, custom hooks, new components, constants, type declarations and/or mock data to separate files.
Establish rules when it comes to organizing code. Make sure each directory and each file are organized the same way. Strive for consistency. Organized and consistent code will boost your performance because you won’t have to scroll through the whole file to find something, you will know exactly where to look first.
We can always apply these tips inside our React components and make them easier to maintain and reuse.