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: