How to Make a Simple Custom useDrag React Hook

Handle element drag behaviors with a custom reusable React hook

Tanner Marshall
JavaScript in Plain English

--

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!

--

--

Creative director and engineer at Osmosis.org. An illustrator/video creator turned web developer; now I create tools that help create engaging videos!