Angelos Orfanakos

More fun with custom React Hooks

Inspired by this FAQ on React Hooks, I came up with a custom hook that lets you provide separate callbacks for when the component is mounted, unmounted, updated, as well as a clean callback for the update:

function useGranularEffect({ onMount, onUnmount, onUpdate, onUpdateClean }) {
  const updateRef = React.useRef(false);

  React.useEffect(() => {
    if (updateRef.current && onUpdate) onUpdate();
    updateRef.current = true;
    return onUpdateClean;
  });

  React.useEffect(() => {
    if (onMount) onMount();
    return onUnmount;
  }, []) // eslint-disable-line react-hooks/exhaustive-deps
}

For the demo, I also wrote a custom useForceUpdate hook to force the example component to update:

function useForceUpdate() {
  const setUpdate = React.useState(true)[1];

  return function() {
    setUpdate(update => !update); // Toggle to trigger update
  };
}

And a custom hook to mount/unmount a node:

function useToggleNode(node, initialState = true) {
  const [toggledNode, setToggledNode] = React.useState(
    initialState ? node : null
  );

  function toggleNode() {
    setToggledNode(toggledNode => (toggledNode ? null : node));
  }

  return [toggledNode, toggleNode];
}

Finally, the rest of the demo code:

import React from 'react';
import ReactDOM from 'react-dom';

// function useGranularEffect(...

// function useForceUpdate(...

// function useToggleNode(...

function Example() {
  const forceUpdate = useForceUpdate();

  useGranularEffect({
    onMount: () => console.log('mount!'),
    onUnmount: () => console.log('unmount!'),
    onUpdate: () => console.log('update!'),
    onUpdateClean: () => console.log('clean!')
  });

  return <button onClick={forceUpdate}>render</button>;
}

function App() {
  const [toggledNode, toggleNode] = useToggleNode(<Example />, true);

  return (
    <>
      {toggledNode}{' '}
      <button onClick={toggleNode}>{toggledNode ? 'unmount' : 'mount'}</button>
    </>
  );
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

You can see it all in action on this Codesandbox: