Create your own React hook
React hooks are a powerful way to supercharge functional components. However, beyond the useState
and useEffect
hooks, the examples for creating your own hooks are not very extensive. Let’s look at a practical example by implementing a file drag and drop on the whole window, which we’ll call the “Window Drop Zone”.
To get started, let's create our hook file. The naming convention for hooks is “use” plus the hook name, so let's create a file called useWindowDropZone.js
with a basic shell function.
When we use the window drop zone hook, we’ll want an onDrop
callback that is invoked when the user drops files. We’ll need to use the useEffect
hook in order to add and remove the onDrop
callback event listener.
The useEffect
hook is documented well, but here is a quick explanation: The first argument is a function that is executed on every render. It can optionally return a cleanup function that is executed before every re-render and when the component is unmounted. In our case, we’re adding the drop
listener to the window and removing it in the cleanup function. The second argument of useEffect
is an optional array of values that the effect depends on. This allows us to conditionally run the effect, instead of executing it on every render. In our case, we are saying only add the drop
event listener when the onDrop
argument changes. This means we don’t have to unnecessarily remove and re-add the listener on every render.
Let's see our new hook in action:
🤔🤔🤔
It doesn’t appear to be working… What’s happening is that the browser is consuming the event and loading our dropped file. In order to fix this, we need to call the preventDefault
method on the event. So we’ll have to supply our own drop
event listener and a dragover
listener to prevent the default event handling. We could easily create our own event listener functions to handle this. However, since we’re expecting our useWindowDropZone
hook to be used in a functional component, that means each render will cause a new function to be created. Thankfully, React already has the useCallback
hook that only creates the function once, unless the dependencies change. Let’s see how this looks inside our hook:
So what did we do? We created onDropCallback
that prevents the default handling of the event and calls the onDrop
argument that is supplied to our hook. Like the useEffect
hook, the second argument to the hook is an array of dependencies. In our case, we only want a new onDropCallback
to be created when onDrop
changes. We also created the onDragoverCallback
that prevents the default event handling and we claim it has no dependencies by supplying an empty dependency array. This means that it will only be created once no matter how many times our useWindowDropZone
hook is invoked while rendering. We also updated the drop
event listener to use our new onDropCallback
function, added a new dragover
listener, and updated the effects dependencies.
Now let's see if our modified hook works:
Success!
What if we could take this a step further and provide a dragging state from our hook? We can do that by returning an isDragging
field from our hook. We’ll have to do some wiring up internally and leverage the useState
and useRef
hooks:
useRef
what!?! An overlooked benefit to this hook is that it allows us to keep track of mutable values while not triggering rerenders. In our case, we need to count how many times we receive the dragenter
and dragleave
events so we can know when the drag has initially entered and finally left. Since we do want to trigger rendering when the dragging state changes, we use the useState
hook. We also added a couple more event listeners and tied it all together. Let’s see our latest changes in action:
It works!
Now that we have a solid functioning hook, we could expand its features to include things such asonDragEnter
and onDragLeave
callbacks.
The true powers of hooks are yet to be fully realized, but with the right know-how, you can start to harness their power. Hopefully, this post will help you in creating your own hooks or expanding on existing ones. If you have any comments, feel free to leave them below and make sure to follow the JetClosing Engineering blog to see some of the great stuff we’re working on.