loading...
Cover image for React useReuse pattern

React useReuse pattern

basarat profile image Basarat Ali Syed Updated on ・2 min read

I'd like to introduce the useReuse 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: 

  • Reuse : the component that you want to be reusable. This will be used by the parent to render the component.
  • useReuse : 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 Counters.

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

Posted on by:

basarat profile

Basarat Ali Syed

@basarat

Microsoft MVP for TypeScript, Cypress Ambassador, open sorcerer, book author, youtuber, eggheader, developer empathiser.

Discussion

pic
Editor guide
 

Cool pattern, i've recently started using it

 

Really nice pattern

But i think -

  1. In counter definition, use should have a default value of useCounter. That way none of the consumers need to explicitly pass it if they just want the default behavior.
  2. This example doesn't really show its benefits. We are not passing any customised logic to both instances of Counter. So, if this was a simple component, it would need no props and still do same thing. If we pass two different behaviours as props in two instances of counter, then only this patterns flexibility makes more sense
 

minor correction:

button onClick={() => {use.setCount(use.count + 1)}}>
Click me
/button

 

Thanks. Fixed 🌹