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
titleprop to theTaskListItemcomponent. It would be nicer if we could pass it like<TaskListItem>{task.title}</TaskListItem>. - We pass the
tasksprop to theTaskListcomponent and just forward the title from it to theTaskListItemcomponent. 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
Last updated on