How to Make a Simple Custom useDrag React Hook
Handle element drag behaviors with a custom reusable React hook
There’re approximately one billion libraries that handle moving and dragging for React floating around the Reactiverse, and a lot of them are actually pretty rad. Sometimes, though, you might not want a fully fledged library, or the libraries you found don’t accomplish what you need, or you just like to learn stuff and want to write your own darn code! 🤓
Something else to point out is that the hook we’ll be building is meant to be used for dragging an element on the page and actually changing its position through a CSS
property. We’ll therefore be using pointer
event handlers (onpointermove
, onpointerdown
, etc.) to move our element . If we were building hook to move data, not the actual element, we’d likely want to employ the drag
event handlers (ondrag
, ondragstart
, etc.).
Without further ado, here’s a super simple custom React hook you can use for all your basic element-dragging necessities.
useDrag
Cool. The hook’s really pretty simple, but let’s run through what’s going on for kicks and gigs.
The first argument is a required React ref
object that holds the reference to an HTML element. Next, deps
, a list of dependencies (important), and finally an options object with a few optional callbacks: onPointerDown
, onPointerUp
, onPointerMove
and onDrag
.
State and Handlers
We store the dragging state in isDragging
, and then declare handlers handlePointerDown
, handlePointerUp
, and handlePointerMove
.
isDragging
simply keeps track of whether or not the element is currently dragging. Notice that it’s also returned from useDrag
for use elsewhere.
For handlePointerDown
, whenever the user presses their device down, we set isDragging
to true
and then pass the event back through our onPointerDown
callback. Similarly, with handlePointerUp
, whenever the user’s device goes up, set isDragging
to false
, and pass the drag event back to onPointerUp
. Finally, with handlePointerMove
, any time the user moves the mouse, pass the event back to onPointerMove
, and if we’re currently dragging, also pass it back to onDrag
.
useEffect
After that we add our useEffect
hook that takes care of registering and unregistering our event handlers. We first check to make sure our element ref
’s been initialized, then we register each event with addEventListener
and our handler functions above. For cleanup, we return a function that calls removeEventListener
on each handler. Remember that returned functions in useEffect
hooks will run before the next update. If we didn’t do this, new event listeners would be registered each time the effect hook ran, which would pile up right quick. You can see this for yourself by commenting out the removeEventListener
calls and adding a console.log
inside handlePointerDown
. You’ll see that every click will result in progressively more logs.
The last bit is our dependency array, we have isDragging
combined with our deps
array, meaning that whenever any of those variables change, the useEffect
hook will run. The dependency array here is important! Each time the useEffect
hook runs, it gets updated function handlers to register with addEventHandler
. If that didn’t happen, we’d end up with outdated functions registered to the events. E.g. if we had an empty array instead—meaning the useEffect
hook only runs once when the component is mounted—the isDragging
variable in handlePointerMove
would always be what it was initialized as—false
!
Finally finally, as stated above, we return an object with isDragging
for use elsewhere.
Example
Nice! Now let’s go ahead and demo this puppy.
I whipped up a React app with create-react-app
and used everyone’s favorite spinny React logo as the target to drag.
To implement the drag, first we declare our ref
container as divRef
, then a state variable to hold the current translate
position of the div. Next we declare our handleDrag
function that gets called from onDrag
inside our useDraggable
. Finally we call our shiny new hook, passing the divRef
, dependency array with our translate
state variable (important), and our handleDrag
function. Now, down below, attach our divRef
to the logo container, and add in transform styles.
And that’s all she wrote! Check it:
Closing thoughts
Of course, and as always, there’re a bunch of other ways you could have accomplished the above. Indeed, for the example used, it’d be quicker to use React’s onPointerMove
, onPointerUp
, and onPointerDown
handlers directly on the container div
and handle drag state in App.js. Or you could create a custom component to wrap the container div
and handle dragging that way.
One benefit of using the useDraggable
hook that I’ve found helpful, however, is that you can define multiple drag handlers on the same ref
container div
, even in different components by passing around the ref
.
For example, you might have one parent component where you define your ref
. Then you pass that ref
down to a child component that contains your target element. You can now also pass it down to siblings components that handle different drag behaviors.
Something like this:
Again, there’re lots of ways to skin a cat when it comes to dragging elements, but that’s a quick and easy way to do so with your very own reusable React hook.
Thanks for reading, hopefully you learned a thing or two! Now go forth and draggeth thy elements!