React Composition
Ruben Suet
Posted on May 2, 2020
In a previous post, I talked about React Context, a native alternative to sharing state like Redux or Mobx, or just to avoiding prop drilling. These other state sharing solutions imply time and effort to set-up and wire the state with your components. On the other hand with prop drilling you get a quick and straightforward solution without setting anything up. And together with this comes a simple, effective, and somehow not known pattern for it: React composition.
This posts motivation comes from a tweet from @mjackson which received many comments giving counter arguments to his idea. In response he decided to do a very nice explicative video about react composition which I recommended you watch as well. Now my intention with this post is to aggregate to these concepts exposed previosuly by mjackson without losing the initial reference. So let's continue on.
What is React Composition
React composition is one of the most basic techniques that we learn when we are working with react. It is mainly the children
keyword. This example (for learning purposes) illustrates this pattern.
import * as React from 'react'
function DisplayCounter ({ children }) {
return (
<React.Fragment>
<h1> My own counter </h1>
<strong> Press the button and see the counter in action </strong>
{ children }
</React.Fragment>
)
}
function Counter () {
const [counter, setCounter] = React.useState(0)
const increase = setCounter(prevCounter => prevCounter +1)
return (
<React.Fragment>
<DisplayCounter>
<p> You have pressed { counter } times </p>
</DisplayCounter>
<button onClick={increase}> Increase! </button>
</React.Fragment>
)
}
I could have passed the counter
prop into DisplayCounter
and would have had no necessity to nest children here but imagine you now have a flow like this:
Counter (state is set) => anotherComponent => ... => displayCounter (consume states)
.
As you can see, you are now sending the prop through 2 or more components. It can easily be nested but now all of them have a strong dependency on that prop that they don't even use and are just passing it into the next component.
You could set up a React Context (or any state management library) but this solution is straightforward and gives me the benefit I was looking for.
Some real example
A header is often a component we can find in many web apps.
I need to pass User info into 2 places: The avatar itself, and the settings dropdown.Imagine we got this Tree component
The Header component is taking care to receive user info, and it spreads through the rest of the tree components.
In the classic approach, It would look something like:
import * as React from 'react'
import { fetchUser } from './someUtilsLibThatFetchesTheUser'
function Header () {
const [user, setUser] = React.useState(undefined)
React.useEffect(()=> {
setUser(fetchUser())
},[])
return(
<React.Fragment>
<Avatar user={user} />
<Menu user={user} >
</React.Fragment>
)
}
Not really nice to see so many user
keywords. Moreover, if you make it with Typescript, you need multiple type definitions for that user object.
The idea here is to avoid prop drilling and make it easy.
import * as React from 'react'
import { fetchUser } from './someUtilsLibThatFetchUser'
function Header () {
const [user, setUser] = React.useState(undefined)
React.useEffect(()=> {
setUser(fetchUser())
},[])
return(
<React.Fragment>
<Avatar>
<img src={user.avatar} alt={user.username}>
</Avatar>
<Menu>
{ user ? <UserInfo user={user} /> : <LogIn/>
</Menu>
</React.Fragment>
)
}
I am still sending one prop of the user, but now it's a single prop. Before probably would have been
The Menu, without composition, would originaly be:
import * as React from 'react'
function Menu ({ user }) {
return (
<React.Fragment>
{ user ? <UserInfo user={user} /> : <LogIn />
<Settings/>
</React.Fragment>
)
}
Why, if the Menu does not need anything from the user, does it still need to receive it and pass it into another component?
With Composition, the Menu would like this:
import * as React from 'react'
function Menu ({ children }) {
return (
<React.Fragment>
{children}
<Settings/>
</React.Fragment>
)
}
Here relies the power, the previous snippet you could like it more or less (No entendi esta primera oración :S). Depending on your situation you could need React Context or maybe even a more complex library, but sometimes only with Composition could do. This Menu
snippet, shows us that the component doesn't need to know about the user object, it's not coupled at all. Children
is as well a really powerful technique for Compound components, which I will explain in another post.
So a few more words about it: notice how we passed from sending the User into the Menu component, and from the Menu into the UserInfo component, to avoiding this 'proxy' and just passing the info and delegating it to the parent component, the Header.
For the Avatar, let's assume the component was just some stylings and was waiting for the image. No need to show some snippet there :)
Conclusion
In my opinion, React Composition is a great tool that can greatly help at the moment of developing. (React docs are encouraging you to use it). If what you put as children is not really huge, Composition is the best technique. Otherwise, if you're children take like 200 lines of code, I would consider another technique like Context. At the end it is just another card in your hand you must learn when it's best to play at a specific moment.
References for this post
See the original post at my blog suetBabySuet
Posted on May 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024
November 27, 2024