Most components should NOT be reused

Tomek Mularczyk
9 min readMar 27, 2022

--

One of React’s best features — and probably its selling points — is component reusability, it’s very easy to create an encapsulated component that can later be reused. If we work on the company project what we might do is create a set of generic components like buttons, forms, sections, listings, etc., and then share it with other developers. What they need to do is just import them, pass a few props, and voilà, it works! We saved them a lot of time, we reduced the codebase size, we kept it DRY and on top of that, the app is more consistent. While in general, this is something desirable it’s more difficult to do the right way than most people think it is. It’s not something that can be fully planned upfront and if done incorrectly it leaves a codebase with a lot of unnecessary complexity, code that’s not well tested and everyone is scared to edit or remove. Creating reusable components requires some degree of foreseeing the future and because we as humans are not good at it most of the time components should not be reused.

A typical component story

Let’s say we are developing an e-commerce store with house-related items. Within the website, there is a page dedicated to furniture for sale, and we are tasked with creating a form on this page that allows users to sell their furniture. The form should include three fields: name, price, and quantity.

We create a component for that, it’s called AddFurnitureForm and we add it to the page. All good.

Initial component implementation.

Soon, we are asked to do the same but for a page with food for sale. This form will have the same fields as the furniture one but with an additional field — termination date. Alright, this should be quick; let’s reuse AddFurnitureForm, as it mostly has what we need. Let’s move it to a shared components directory, call it AddProductForm, and handle that optional field with some props and conditional statements.

A few days pass, and we are asked to change the submit button to say “Sell furniture” and “Sell food” depending on which page we’re on. Cool, let’s add one more conditional statement to change the submit button text.

Later, it turns out that the quantity field should be optional for the furniture form… one more tweak and it’s done.

Now, after a month, final requirements come in and we’re asked for a few minor changes:

  1. The button should have a different color for the food form (yellow).
  2. The forms should have the titles “Sell Your Furniture” and “Sell Your Food,” respectively.
  3. Additionally, the food form should include a category dropdown.

The final implementation could look somewhat like this:

when the complexity creeps in…

The component has started to look complex. I understand that you would probably handle it with fewer props or move some logic out of the component. You may argue that you would never allow for something like that in a PR, but in reality, components often evolve in this way… the key factor here is time.

It’s complex even though it handles just two business cases. Now, imagine that there’s a new section on our website with electronics. Once again, there’s a request to add a form for users so they can sell their stuff. The form will be very similar to the one for food and furniture, but with one additional field and some minor styling changes. So, sprint planning comes in, and there’s a new developer who gets this ticket assigned to him. And what do we tell him? “Don’t worry, this should be quick. There is already an implemented component that does most of the things you need to do. Everything is styled, validated, and so on; you can reuse it.”

Over time, the component becomes spaghetti.

You might be thinking, “You’re violating the Single Responsibility Principle,” which is true, but I’ve seen it happen so often that I believe there is a stronger narrative here.

I haven’t even mentioned how painful it is to write good typings with these sorts of components, let alone test them.

Few problems with components/ directory

What is the most common directory you can find in React projects?

The famous src/components directory — a big bag of everything. It usually goes like this: we start developing an app, create, let’s say, a homepage, and we think, “Hey, some of these components we created might be reused someday.” So we make them smart, flexible, generic enough, and then we put them in the components/ directory. Our homepage.jsx alone is something that in React nomenclature could be called a “container” component — it mostly fetches the data, does some logic, and renders components imported from the components/ directory.

We then add another page: userProfile.jsx, dashboard.jsx, appSettings.jsx, productListing.jsx, etc., with components like Article, Button, List, SettingsDropdown, NavBar, Footer, ProductCarousel, Map, and so on, which we also put in the components/ directory where they can now be easily reached and reused by others. It feels satisfying and productive.

But the components directory quickly grows big and can be described as a directory of everything that has a JSX in it. So what we do is group components! A practice I’ve encountered a few times is to use an Atomic design:

src/components/
-atoms/*
-molecules/*
-organisms/*
-templates/*
-pages/*

Seems more organized, doesn’t it? Besides that, no one ever knows exactly which component should go where; there is some impression of order here.

It is important to note that “components” is a public directory. Its intention is that it is supposed to be sort of like a MaterialUI or React-Bootstrap library so that people can import these components from anywhere.

Because we essentially invited others to reuse our components by putting them into a public directory, some of them are true Frankensteins with weird logic and different parts of the app relying on them.

Here’s one more problem — website changes. Some pages are being removed, and some components are no longer needed. For example, we might have removed referralProgram.jsx page. Did you then remember to remove related components that are no longer needed, or did you leave them there because: a) they are on the other side of the app, b) they might be used by some other page or component, c) they still might be used in the future, it’s a waste to delete them, or d) you forgot to? One more problem: Let’s get back to the previous example with a shop website. Some time passes, and it turns out that selling food is not good for our business, so we need to delete the food listing page along with the form in it. How confident will you be in removing all the related logic in the form component? Or will you just let it stay there as it’s not breaking anything?

And on top of it, will you remember to check these sorts of problems during someone else’s code review?

Once more, I believe the fundamental notion of reusability is leading us toward poor architectural decisions.

I don’t want to use your components

By optimizing for reusability, we project into the future that someone will want to use our code and that it will be needed.

Working with code shared within the app can always have unintended impacts in distant places. Who knows how it’s being used? Usually, it’s safer to extend them further and further with more lines of code than to perform real refactoring there and in the places using it.

I don’t know why, but developers are usually afraid to repeat themselves, as if this meant they are immature engineers who should go back to year one and learn “Clean Code” rules.

Or maybe it’s more experienced devs who fear Junior Devs coming in and breaking everything, so they try to avoid it by creating a set of components that Junior devs SHOULD use and not creating anything new that could potentially be problematic and not live up to codebase standards.

But what is far more often problematic than duplicated code is a bad abstraction, and it’s a process that is hard to reverse.

Collocate

Instead of building re-usable software, we should try to build disposable software.

When business requirements change, it’s often much easier to remove something and rewrite it than to learn and maintain an old piece of the codebase. You’ve probably had a situation where you thought, “Gee, this is crap. I wish I could remove it and rewrite it from scratch; it would be much easier… But this thing is so coupled with other parts of the system that I’m too afraid to touch it.”

Optimize from the very beginning to be able to delete code … as opposed to planning for a change that will happen in your system.

When I approach a new project, I usually think three times before making some components public. I need to have a strong reason to do so. If not, I make them private to the parent component, and then I make this parent component private to its parent component. Usually, everything is collocated onto a single page.

It’s liberating knowing that you can point to a directory, like a homepage, just press CMD+Backspace, and everything related to the homepage is now gone.

Or when you work on a page, edit the components there, and feel safe that you won’t mistakenly break stuff on other pages.

I’m also not worried that some code is duplicated with the dashboard page. I can go wild there and maybe experiment because what happens in the homepage/* directory stays in the homepage directory. New devs don’t need to learn as much about the codebase and the review process is simpler.

I also don’t have to think hard about inventing unique names for components like List.tsx because the purpose of List.tsx can be deduced from the context of where it is located.

One thing I’ve also noticed is that it is easier to maintain focus when you don’t have to jump through the codebase and files that are very related yet separate in location.

That doesn’t mean I won’t ever have shared, reusable components. I don’t want to write a NavBar again and again for every page. I will have the NavBar component with the required bare minimums, and I will import it from a directory like components/*.

I didn’t say that you should not create reusable components, but that you should think a few times before doing so. What I’m essentially saying is that creating a good abstraction, with a good public API, is a hard thing to do. There is a lot at stake here. Just think of a library like React-Bootstrap or Material-UI — they are collections of dumb simple website components. Yet, if you keep an eye on the issues tab in their Github profile, it seems that work around these libraries is never over.

Btw. I highly recommend that you learn about compound components and inversion of control.

Don’t try to be smart.

So, what went wrong with the example at the beginning? We tried to use the same code for two different user features — adding furniture and adding food. They look similar but they are not the same thing; they should be separate (AddFurnitureForm, AddFoodForm), but we mixed them. Now, when one of the business parts started to change, we had to adapt it so that it doesn’t break the other — what should be a simple change from the business perspective becomes a problematic change within the code. In Domain-Driven Design, two Objects can be named and look very similar, but because they are in different Business Contexts, they are separate beings (separate pieces of code).

Write Everything Twice (WET), and once it’s obvious that some parts are common, extract only the sub-parts that will probably never grow in terms of scope (basically, the things that you will find in popular component libraries). And then build your app on top of that. Don’t try to be smart, do not over-engineer, stop being obsessed with reusability; you will prove your skills on another occasion.

RESOURCES:

--

--