I'd like to introduce the useComponent
pattern, something that has seen a lot of traction in the components we have been building lately.
Objectives
Some common objectives for good React components:
- Components should be reusable.
- Components should be controllable by the container.
Pattern
You provide two items:
-
Component
: the component that you want to be reusable. This will be used by the parent to render the component. -
useComponent
: a custom hook that returns everything that the component needs in order to function. This is meant to be consumed by the parent that wants to use the component.
Motivation Example
Consider the humble Counter
component:
function Counter() {
// Some hooks the component needs
const [count, setCount] = useState(0);
// The rendering of the component
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
This component although functional is not really reusable as it locks its logic within the render function. Say you wanted to use two counters and display the total count, then you are 💩 out of luck.
Motivation Solution
Solution: Move any logic the Counter
component needs into a useCounter
custom hook function.
Here is the useCounter
/ Counter
(and inferred TypeScript CounterProps
🌹) combo:
// Some hooks the component needs
export function useCounter() {
const [count, setCount] = useState(0);
return {count, setCount};
}
// Infer the props
export type CounterProps = {
use: ReturnType<typeof useCounter>
}
// The rendering of the component
export function Counter({ use }: CounterProps) {
return (
<div>
<p>You clicked {use.count} times</p>
<button onClick={() => use.setCount(use.count + 1)}>
Click me
</button>
</div>
);
}
Demonstration of reuse
Say you wanted to use two counters and display the total count. Easy peasy:
export function App() {
const counterOne = useCounter();
const counterTwo = useCounter();
return (
<div>
<Counter use={counterOne}/>
<Counter use={counterTwo}/>
{/* Able to use the counters for any additional logic */}
<div>Total Count: {counterOne.count + counterTwo.count}</div>
</div>
);
}
Note that the useCounter
function can easily take initial values. You can also create local functions in the App
component that can intercept any calls made by the individual Counter
s.
Reasons for its popularity
There are two reasons why its so popular:
- Easy to understand: You are writing the component as would naturally, just splitting it into logic and rendering functions. This also makes it easy to deal with while developing and doing code review for business logic and how it looks.
- Uses only React: Your components function without any third party library dependency. This allows much greater reuse across teams.
PS: a video on comparing mobx and hooks : https://www.youtube.com/watch?v=MtVGDAnveuY
Top comments (4)
Cool pattern, i've recently started using it
Really nice pattern
But i think -
minor correction:
button onClick={() => {use.setCount(use.count + 1)}}>
Click me
/button
Thanks. Fixed 🌹