Applying SOLID To React
SOLID principles were developed to help the longevity of your code, but they are not simple to understand and even more difficult to apply to frontend development. Use this article as a quick reference guide on how to start applying the SOLID principles to your React+Typescript project.
A class should only have a single responsibility, that is, only changes to one part of the software’s specification should be able to affect the specification of the class.
Complexity and size are always related to low cohesion, but it’s hard to decide how many responsibilities a component should have. A great rule of thumb is: every component should solve a specific problem.
This rule can be applied to many parts of your code: from having separate components doing specific view tasks, to how your application is structured. React docs has a good example :
Every color should be a separate component that matches an individual piece of your data model. But what about fetching data? It’s definitely not a concern of the red component, and neither will the orange container be concerned about fetching. Its job is to trigger it to another layer that handles HTTP requests, mapping, changing your application state, and etc.
Separating the data layer into a different component solves any problems with accidental duplication, since you might use the same dataset on other components.
Having multiple small view components solves another issue: Merges. Imagine if the search results page (image above) is a single component that is being worked on by two frontend engineers at the same time- what are the odds of having merging conflicts?
In the above example, the same component holds state, fetches data, and renders the result. That’s way more responsibilities than it should have, and this component will be very fragile in the future after multiple changes.
“Software entities … should be open for extension, but closed for modification.”
It’s hard to talk about open-closed principle without remembering Kent C. Dodds’s “apropcalypse” :
Unfortunately, truly maintainable, flexible, simple, and reusable components require more thought than: “I need it to do this differently, so I’ll accept a new prop for that.”
Take the following example:
Seems pretty standard right? But this component can grow into something like this:
So the right way to start a extensible component is to use composition from the beginning. This way, the Input component would be closed for modifications and open for extension:
Liskov substitution principle
“Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
The main takeaway from this principle is to use TypeScript. You can easily swap components if they share the same contract.
In the following example, both components expect the same interface:
Interface segregation principle
“Many client-specific interfaces are better than one general-purpose interface.”
Keep your interfaces small and cohesive. It’s not worth reusing the interface from your data mapper just because its easier. A component interface should only have the properties that are relevant for it.
This also helps for your IntelliSense. When you have a huge dropdown on your IDE and it cant autocomplete properly, you are probably not segregating your interfaces.
Notice the interface “CatFactData” has both a color and link in the above example, which is not being used by a “CatFact” component. Not destructing it doesn’t mean this component won’t depend on it, an interface shouldn’t have unused properties.
Dependency inversion principle
One should “depend upon abstractions, [not] concretions.”
In the example below, we directly refer to axios in a page component, which also directly derives from the axios interface:
We can avoid directly depending on axios by creating a Factory component. This way our dependency will be an Abstraction which returns the same interface defined by our Service:
Note that we can also provide fake data, not just dispatch axios.
Our simple Service component, which in this case holds the URL and does some mapping for the sake of simplicity, is casting whatever response it gets to a type, which means we are not deriving its interface from axios in any way.
See the full example here.
Whats next ?
If you are tired of the YAGNI Principle (You aren’t gonna need it), definitely try SOLID for a while. But as Robert C. Martin himself wrote :
“a dogmatic or religious application of the SOLID principles is neither realistic nor beneficial”
That means you as a software developer should draw your own conclusions and do your own research. If you like what you read here and you think it could be beneficial to your project, try it!