Component Composition
The power of composition.
We really hate the fact that the new React docs do not contain the Composition vs Inheritance section from the old docs.
Either way, we will show you how to use composition to improve your code.
What is Component Composition?
Component composition is the idea of using a component inside another component. This compelling idea allows you to build complex UIs out of simple components.
We did this in the last section when we refactored the label, the input, and the button into a single component called AddTask
, which we then used inside the App
component.
But let's look at a more specific case of composition.
TaskList and TaskListItem
We currently have a list of tasks we're rendering in the App
component. Let's extract that into a separate component called TaskList
.
Now, we can use this component inside the App
component.
Let's also extract the TaskListItem
component from TaskList
.
Now, we can use this component inside the TaskList
component.
Our code is now more modular and easier to understand. Our UI is now composed of smaller components, too.
This refactoring sounds okay, but we are passing a lot of unnecessary props compared to our initial implementation and lose some natural semantics in our code.
- We pass the
title
prop to theTaskListItem
component. It would be nicer if we could pass it like<TaskListItem>{task.title}</TaskListItem>
. - We pass the
tasks
prop to theTaskList
component and just forward the title from it to theTaskListItem
component. It would be nicer if we could pass it like<TaskList>{tasks.map(task => <TaskListItem>{task.title}</TaskListItem>)}</TaskList>
.
Let's remove these unnecessary props by using composition.
Refactoring TaskListItem
Instead of passing the title
prop to the TaskListItem
component. Let's have a more natural API for it. We'll pass the title as children to the TaskListItem
component. Read more about passing JSX as children.
Also, the React.PropsWithChildren
type is generic. We can pass the props type to it like React.PropsWithChildren<TaskListItemProps>
. This is equivalent to TaskListItemProps & { children?: React.ReactNode }
.
Now, we can use the TaskListItem
component like this.
Refactoring TaskList
Now, let's refactor the TaskList
component. Instead of passing the tasks
prop to the TaskList
component, let's pass the JSX as children to the TaskList
component.
Now, we can use the TaskList
component like this. Remember to import the TaskListItem
component.
This code feels more natural and we're not passing any unneccessary props.
Technically speaking, we are still passing the same props but in a different way.
Let's see how powerful our composition is now. Assume we must show the number of tasks in the TaskList
component. We can easily do that by adding a new component called TaskListHeader
and using it inside the TaskList
component.
Now, we can use this component inside the App
component.
But we have a problem. The TaskListHeader
component is not a list item inside the TaskList
component. We can do this by creating a slot
called header
prop in the TaskList
component and passing the TaskListHeader
component to that slot.
Note how we pass the TaskListHeader
component as a header
prop to the TaskList
component. Many folks do not know that you can pass ReactNode
as props to components. This is how real composition works and is very powerful.
Performance Benefits
There are also performance benefits to using composition. Let's say our TaskList
component has a simple timer that updates every second. This would require state and effect in the TaskList
component, but would that re-render the TaskListItem
or the TaskListHeader
component? Let's find out.
Let's add a console.log
in the TaskListItem
component.
What do you see in the console? You should see that the TaskListItem
component is not re-rendered. This is because React re-renders only if the state or props change. Since the TaskListItem
component has no state or props that changed, it is not re-rendered.
This is a very powerful feature of React and can take time to wrap your head around. But once you do, you can write performant React code without fancy optimizations. We recommend reading the two articles below for a more detailed explanation.
Great, you can remove the console.log
statements.
In the next section, we'll summarise and leave you with a few suggestions for continuing this project to end up with a resume-worthy project.
At this point, your code should be a good match to the branch of the repository: 8-component-composition