Search code examples
javascriptreactjsinheritancecomposition

How to share hooks between React components with composition


I have a higher order component that adds universal functionality to a group of similar components. For the purposes of discussion, I'll call the HOC Wrapper and the children CompA, CompB, etc.

Wrapper is connected, and handles the interactions with the Redux store, as well as API interactions. The data it sends is owned by the wrapped children, so I pass the API/dispatch function(s) from Wrapper into the children via props. Now CompA, CompB, etc all have a props.doStuff function that's defined one time in Wrapper.

This is OK, but the events that determine when to call the doStuff function are changes of the local state in CompA, CompB, etc. So I'm copy/pasting the same useEffects between the children...

function CompA(props) {
  const [data, setData] = useState({});
  useEffect( () => {
    if (props.doStuffNow === true) {
      props.doStuff(data);
    }
  }, [props.something]);
  return <div>CompA</div>
}

function CompB(props) {
  const [differentData, setDifferentData] = useState({});
  useEffect( () => {
    if (props.doStuffNow === true) {
      props.doStuff(differentData);
    }
  }, [props.something]);
  return <div>CompB</div>
}

This is not D.R.Y. and I'm really tempted to move the useEffect etc into Wrapper class and allow CompA and CompB to extend that class.

However, the React documentation strongly recommends composition over inheritance using higher order components. The linked article goes so far as to say they've never encountered a situation in which inheritance was better than composition.

So what am I missing? Is there a way for me to make this code DRY using HOC? Can I somehow pass the useEffects via props? Is the sky going to fall if I just follow my instinct: class CompA extends Wrapper... ??


Solution

  • Create a new hook called useDoStuff that performs the useEffect hook and then useDoStuff(props) wherever you need it.

    function useDoStuff(props) {
      const [data, setData] = useState({}):
      useEffect(...)
    }
    
    function CompA (props) {
      useDoStuff(props);
      ...
    }
    
    function CompB (props) {
      useDoStuff(props);
      ...
    }