We're always excited about the new shiny product releases that drop every week in the programming world, from new ways to structure components to new code refactoring techniques to reduce those two lines of code.
But in the real world, things are not so shiny. We often have to deal with codebases that have evolved over many years and components that bear the marks of many different developers.
Our story is about a component with 2700 lines of code. That's... way too many. Let’s try to explain how things went south and how we can do better.
I currently work for a company that maintains a fleet management dashboard that shows vehicles roaming around a city in real time.
This dashboard component is the hero of our story today. It has many functionalities, but none of them are super fancy:
- A map that shows vehicle markers
- A way to search for the vehicles
- A popup that shows each vehicle’s details when clicked
- A list of vehicles at the bottom
- Some filtering options
Sure, there are several features that this single component is responsible for displaying. But are there really enough to require 2700 lines of code?
Obviously, it would be stupid of me to paste 2700 lines of code into this article. (Not to mention illegal! 😓) But, let me show you, generally, the react component's structure:
Okay, now tell me what’s wrong here.
To be 100% honest? Almost everything. Let me explain:
This is an obvious one. Unfortunately, I've seen many examples of constant declaration across many companies and many components.
Keep the constants in a separate file.
It doesn’t matter if they’re being re-used or not, it’s still better to store constants in a separate file. Down the line, someone else will create a separate constant with the same value, which will eventually create confusion.
I do it this way:
Then I import them into my component:
For this particular React component, it isn’t a big deal, but best practices are best practices.
Styles and Helper Methods
I think it’s a very common mistake (and sometimes an allowable thing to do!) to put the styles and helper methods in the same file for smaller components.
If your component is only 30–50 lines of code, it can make sense to keep the styles and helper methods in the same file.
But, this isn't true for big React projects. 580 lines of style declaration doesn’t make sense in any scenario, as you won’t need to touch these styles very often.
I follow the following folder structure to keep things organized:
The responsibility of the files is clear from the names of the files themselves. It’s simple to split our massive component to one-third of its current size just by putting things where they should be! Refactoring React components doesn't have to be daunting.
If you are using Raw CSS or SCSS, you probably don’t make this mistake, but projects using styled-components or material-ui mostly follow the above bad practice.
Once someone started it, it became the standard practice.
Don’t fall into this trap! Create a separate file for styles ahead of time; this can save your component in the future.
There are two types of components:
- Dumb components → Only act as a container or view
- Intelligent components → Show something based on logic
Let's talk about React components' structure: There is no reason to put two components in the same file. It directly violates the Single Responsibility Principle.
Every class or component should do one thing and one thing only.
Sometimes we get lazy, myself included, and put simple container components into the actual component. But, what will the next developer think when they read this component?
Will they move the smaller component into its own file?
Umm… Probably not. So after 4–5 years, you’ll have 200 lines of helper dumb components that could have been easily extracted into separate files, and it will take you much longer to refactor React components.
React Class Component
I’m not sure if you noticed, but this massive component is using ClassComponent. I’m sure you know why; it was written when functional components were not that common.
Nowadays, using functional components makes more sense:
- They’re easier to maintain
- They use less code
- They’re (arguably) more performant
But even I wouldn’t try to convert our 2700-line component into a functional component at this stage. We need to do a lot of code refactoring before converting it into a functional component.
Results of Easy Refactors
Let me show you an estimate of how much we can improve this component without even understanding its function.
If all we do is export the constants to constants.ts, the styles to styles.ts and the helper methods to helpers.ts, we can reduce the component by 1400 lines.
This will take us about two hours of work; all we need to do is put things into the appropriate files and then import them.
We can reduce our component from 2700 lines to 1300 lines!
Some may say that’s still a lot, but hey!!! One step at a time, right?
Can We Do Better?
Yes, of course. When we look into the internal logic, we can reduce the component even further if we:
- Break the actual components and re-usable parts into even smaller components
- Use a functional component
- Take advantage of hooks
- Use functional redux
And so on… But that’s a React refactoring story for another day.
Show Me the Good Parts
Obviously, this component has lots of problems, but it has some good things going on, too.
Although the type declarations add up to 200 lines, they’re worth it. Without Typescript, it would be impossible to maintain this component.
Some of the dumb logic is extracted from the view logic itself. For example, showing a message based on vehicle status looks like this:
It’s better to have them in a separate function instead of writing the logic into the view, which can look something like this:
So, it’s not all bad, and some developers definitely tried to do things the right way. At the end of the day, development is a team effort.
What Did I learn?
In our discussion of how to refactor React code, the biggest takeaway for me is the importance of following best practices.
Best practices are there for a reason!
The impact of following best practices may not be evident on the first day, but if you disregard them, you will feel the pain someday; code refactoring takes more time than following best practices right away.