From the

Mar 12, 2021

Balancing Reuse and Duplication with React

ReactReuseDuplicate
Jordan Nielson
By Jordan Nielson

Working with Components in a React project I usually reach a point where there are pieces that I need to add that are similar to components that exist, but not 100% the same. There might be slight differences in what data is available or the order it is presented. When dealing with code, this leads to an interesting problem of deciding between adjusting the existing component to be re-used or copy/pasting and making the small changes to end up with 2 similar components instead.

When I say 'adjusting the existing component' that usually involves adding 1 or more props and using those props in a bunch of conditional logic to handle the two similar situations (the new one and the existing one). For simplicity in this article I'll refer to this as reuse.

For 'copy/pasting and making small changes' that usually means taking what was there and adjusting it to handle the new case and removing pieces that are no longer needed. For simplicity in this article I'll refer to this method as duplicate.

Both of these methods (reuse and duplicate) have pros and cons, but I generally prefer these methods to starting from scratch if there's an existing component in the project.

Thanks to version control software like Git, it's relatively painless to switch approaches if I find that I picked the wrong one to start with. The biggest downside of pivoting to the other option is the lost time. That time can be a significant factor in decision making when projects are on a timeline, but even if you chose the wrong approach .

In order to make the decision of which approach to try first (and hopefully spend the minimum required time on the feature), I dive into the code of the existing component and look around to see what indicators exist in the component and surrounding code that would make one path better or easier than the other. Before I get into those indicators let me share an example from a recent project.

When working on a codebase, there's a couple talks from React Rally in 2018 that I find inspire me to worry less about "code quality" and focus more on what matters - shipping the product. Michael Chan's talk Hot Garbage Clean Code is Dead and Kent C. Dodds' talk Simply React both have great principles covered in them.

Advanced Search - An example in reuse

A recent project I worked on had designs for an advanced search bar on 3 different pages. Each page was working with a different kind of data, which meant that there would be some fields to search on that were shared while others were unique to the page. For example, each page allowed you to search the data by the created and updated dates. Meanwhile, each page had unique fields conveying 'status'.

Someone else on the team had created the advanced search for the first page that needed it, and I ended up being tasked with the other 2 advanced search bars. This meant that I knew I would be doing this same type of thing twice, so it was worth the time to plan ahead and dig into the existing components to evaluate if I should reuse or duplicate.

Indicators to reuse

When digging into an existing component to figure out if I might be able to reuse it for a similar feature there's a number of indicators, I'll phrase these as questions. I generally use these indicators to inform a choice between reuse and duplicate, so this section will have that framing.

  • How does the data flow through this component and is it able to handle the reuse case?
    • (When using TypeScript) Are the typings simple enough to reuse?
  • How many imports already exist and are they directly reuseable?
  • How many conditional branches already exist indicating how much this component has already been reused?
  • How safe is it to edit this component?

Data flow and typings

Data flow and typings are critical things to consider when determining if you can reuse since they are likely to be the first thing that makes it impossible or really hard. The answer I find to this question is usually one of two options:

  1. "The data is coming from spreading a huge object that has tons of data which this component sends to the right spot"
  2. "The data is specifically passed in as few pieces as possible for this component"

There are always tradeoffs so neither approach is inherently bad. I've found it much more difficult to reuse the first instance especially when using TypeScript, since the typings to allow spreading huge objects I usually can't find a way to simplify (Generics exist but those aren't usually considered "simple").

Imports - Utility Functions and Components

Generally speaking with React Components the main imports will be other components or utility functions. Utility functions tend to be easy reuse cases since they're designed that way, but other components can be really interesting. When there are other components I find that I need to examine each one independently with these same indicators. The reason for this is that each component could also become a reuse or a duplicate depending on the scenario.

For example, with the search field the Input component was an easy reuse since it had been designed to be super flexible yet specific with how it handled data. Meanwhile the Status component (used to pick which "status" values would be included in the search) was a duplicate case since the values to display (which was all the component did) were specific to that data type.

Conditional branches

In my experience with reuse it usually results in additional conditional branches in the code to handle the 'similar but not the same' aspect. I have this indicator in my list because I've run into cases where a component is relatively simple to reuse for the second or third usage, but eventually the logic in that component becomes complicated enough that I find it better to duplicate and simplify again. For example, a component might have started by taking 4 or 5 props to get all the data required in. Then it was reused to add an optional prop that adds an additional bit of text in. Then maybe an optional image was added. Eventually you reach a point where there's more optional props than required ones (looking at you Hero components) and it becomes simpler to duplicate.

Safety when editing

In some projects, especially ones that don't have robust test suites (which we all want but which few projects seem to have), there's an important factor of 'safety' when editing things that might already be in production. For example, changing the root component of a section of the application might be something you don't have time to do since you would need to test that entire section again. But, if your testing is largely automated and, more importantly, gives you confidence that would be less of a concern.

Similar Detail Pages - An example in duplicate

In that same project there were two similar pages showing a 'detail' view of a data type. The pages had the same layout, major features, and were pretty much the same outside of one key point - the actual data fields used had different names! Each page interacted with a different service in addition to the application's backend for data, which resulted in entirely different data structures. Since the project utilized TypeScript, as I mentioned in the indicator to reuse this singular difference made it nearly impossible to reuse any significant chunk of the components between the two pages. This resulted in a bunch of duplicate components, which worked out fine for the project. This was also a duplicate choice since it was much safer to avoid editing the already-in-production root component for the detail page.

Indicators to duplicate

When deciding to start with duplicate as the method for a similar feature, I usually utilize the same indicators that I already talked about in reuse. Duplication is a 'hot topic' in programming, which generally leads to people avoiding it. I'm not going to get into that discussion in this article, but I would encourage you to check out Dan Abramov's talk the WET codebase.

When approaching a task in a project you (or someone familiar with the project) usually has a gut feeling on if duplicate or reuse should be tried first, which impact the angle I answer the questions with. For example, if I feel like a feature will likely be a duplicate case I utilize these questions:

  • How can the data flow through this component be cleaned up or simplified using duplicate?
    • (When using TypeScript) How can the typings be simplified through using duplicate?
  • How many imports already exist and do they need to be duplicated?
  • How many conditional branches already exist and could be removed when using duplicate?
  • How much safer is it to not touch the existing component?

The reasons behind most of these questions are largely the same as the last section, just flipped to focus on simplification which is usually something that can be accomplished though taking a second pass at a feature.

It's a spectrum!

Thanks to the power inherent in the Component model which React utilizes, reuse and duplicate are on a spectrum! Most tasks will likely use both strategies since pieces that are easier to reuse could be extracted as a component while pieces that are more difficult to reuse will utilize the duplicate method and focus on composition instead of inheritance.

;