Article thumbnail

Make your components healthy with these patterns

6m
components
refactors
patterns

Intro

We'll review the ugly Comments component and then we'll perform a refactor with content projection techniques. We'll create a reusable List component.

Few words about healthy components

The best component is the one, that can be used in any application. In addition, it should have:

  • simple and easy to understand API
  • type definitions in TypeScript or any other technology
  • should be easy to extend without modifying the internal implementation
  • should have the tests - it may be in "Jest" or any other technology

Just like humans, our components can have symptoms that indicate a disease. Yes, I like metaphors... So in simple words, the perfect example of a healthy component is one, that can be ctrl+c and ctrl+v to other applications and it will work without any additional effort - just like a library that you are installing to your huge JavaScript projects.

That definition is invented by Me - just to show what is exactly important in implementing good components, decoupled from the application domain.

Example of coupled and badly designed component

Respect your time. Think about that. If you're spending time to implement a component and you will do it badly, you'll never again be able to use it without big changes.

Look at following one. I marked with ❌ the parts that are bad. Later we'll improve them.

Loading

And that's how the usage may look like

Loading

Firstly, we need to understand the responsibility of this component. It should render the list of comments and provide a header at the top, and a footer at the bottom.

We need to provide the changes to decouple it from the application domain. We can use the content projection technique with the children property as a function - it's often called function as a child pattern.

So, let's create a new component called List - why do we need a new one? It will be explained later ☜(゚ヮ゚☜).

Designing reusable and decoupled List component

We need a new component because we don't want to mess up the currently implemented Comments one. So, the plan is to create a List one, then check if it works separately ok, and then we'll use this List component inside the Comments component.

That's how it will be used:

Loading

Loading

Loading

Look how we injected the model - Comment into the component. It's insanely safe to use right now. If you'll try to use not allowed property in the interface, TypeScript will warn you!

Creating type definitions for the List component

As usual, I'm starting from type definitions to be able to catch weird use cases. In addition, later I can reuse them in other component variants or maybe in components that consume the implemented one.

Loading

We've used generic - T and type constraints - extends to verify a given generic type. It must have at least two properties - id and content.

Another interesting part is the type definition for ListItem. It will be a function that takes props and returns a ReactNode.

It will be completely clear what is going on when we start using these models to implement this component. Don't worry (~ ̄(OO) ̄)ブ!

Let's implement the List component

Look at the following code in which we just imported previously created models, and compare it with the previous, starting implementation - a lot simpler, and you can pass your own header, footer and you can define the ListItem component.

Loading

Now we can create as many List variants as we want, and we don't have ugly callbacks passed to the Comments component as previously. This code shows how greatly designed React is. You can compose any feature with greatly written components.

How to use it? You've already seen this but let's check again!

Loading

Making the List component more customizable

How about passing our own className, events, or other properties to the container node in our List component? Yea, that's what is missing! Let's extend the basic ul node props that React provides.

Loading

The last improvement and we will be ready to go! We need to use the spread operator and rest operator to maintain additional props and assign them to the div wrapper.

Loading

Now we can pass any properties to the List component. Events, id, className, and everything that is defined in our interface.

Loading

I skipped tests for this component, it's not a subject for this article. If you are interested about testing feel free to check following course - React testing spellbook.

The full example on Codesandbox

Conclusions and thoughts

The result is simple - we can create as many List variants as we want. Now you know how to decouple your components from the application domain and how to use techniques like content projection, slot pattern and function as a child to make your components more flexible and reusable.

If you are interested in articles similar to this one, I can recommend the following stuff:

Let's visualize the data on timeline - Creating React timeline component.

Let's display fancy snippets - React code snippet component.