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.
Before we delve into the content of article, it's essential to underscore the context. Our primary objective is to create highly reusable components. The utilization of these patterns is particularly beneficial when prioritizing maximum reusability, and it's important to note that the provided abstractions may not be necessary when crafting conventional application code. The examples we are about to explore are especially valuable when your goal is to develop a comprehensive reusable components library.
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
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.
And that's how the usage may look like
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:
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.
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.
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!
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.
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.
Now we can pass any properties to the List component. Events, id, className, and everything that is defined in our interface.
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.