What We Do

Company

Resources

Events

Blog

Free Consultation

ahoy@headway.io

(920) 309 - 5605

7 min
Creating Custom React Hooks with useHug
Subscribe

Creating Custom React Hooks with useHug

Chris Held
Development Lead

Asking Better Questions About Your Product

Download our free guide to begin implementing feedback loops in your organization.

By filling out this form, you agree to receive marketing emails from Headway.

Scaling products and teams is hard.

In this free video series, learn how the best startup growth teams overcome common challenges and make impact.

Scaling products and teams is hard.

In this free video series, learn how the best startup growth teams overcome common challenges and make impact.

You don’t need developers to launch your startup

In this free video series, learn proven tactics that will impact real business growth.

By filling out this form, you agree to receive marketing emails from Headway.

Make better decisions for your product

Dive deeper into the MoSCoW process to be more effective with your team.

By filling out this form, you agree to receive marketing emails from Headway.

A mindset for startup growth

In this free video series, learn the common mistakes we see and give yourself a greater chance for success.

By filling out this form, you agree to receive marketing emails from Headway.

The ultimate UX audit kit

Everything you need for a killer DIY audit on your product.

  • UX Audit Guide with Checklist
  • UX Audit Template for Figma
  • UX Audit Report Template for Figma

Building custom hooks is a great way to encapsulate behaviors and reuse them throughout your application. To demonstrate this, we're going to build out the idea of "hugging" elements of our UI.

Our huggable behavior will:

- Change the mouse cursor on hover (we want our user to know what needs a hug)
- Scale the element down on click (this is a firm hug, some squishiness is expected)
- Change the mouse cursor while clicking (to show our appreciation)

Get started

I find the first step to making something reusable is to use it once, so let's implement this in a component:

   -- CODE line-numbers language-javascript --
   <!--
     import React, { useState } from "react";
     import { animated, useSpring } from "react-spring";

     const Huggable = () => {
       const [hovering, setHovering] = useState(false);
       const [pressed, setPressed] = useState(false);
       const animationProps = useSpring({
         transform: `scale(${pressed ? 0.8 : 1})`
       });
       const onMouseEnter = () => setHovering(true);
       const onMouseLeave = () => {
         setHovering(false);
         setPressed(false);
       };
       const onMouseDown = () => setPressed(true);
       const onMouseUp = () => setPressed(false);

       let className = "huggable";

       if (pressed) {
         className += " hugging-cursor";
       } else if (hovering) {
         className += " huggable-cursor";
       }

       return (
         <animated.div
           className={className}
           onMouseEnter={onMouseEnter}
           onMouseLeave={onMouseLeave}
           onMouseDown={onMouseDown}
           onMouseUp={onMouseUp}
           style={animationProps}
     role="button"
         >
           Hug me!
         </animated.div>
       );
     };

     export default Huggable;
   -->

There are a few things going on here so we'll take a closer look:

   -- CODE line-numbers language-javascript --
   <!--
     const [hovering, setHovering] = useState(false);
     const [pressed, setPressed] = useState(false);
   -->

There are two states that we want to track here, is the user hovering and have they pressed the button.

   -- CODE line-numbers language-javascript --
   <!--
     const animationProps = useSpring({
       transform: `scale(${pressed ? 0.8 : 1})`
     });
   -->

Leveraging useSpring for animation

We take advantage of react-spring's `useSpring` hook to create an animation. We could also use CSS transforms here but react-spring does a lot of math for us to give us really good looking animations without much work.

   -- CODE line-numbers language-javascript --
   <!--
     const onMouseEnter = () => setHovering(true);
     const onMouseLeave = () => {
       setHovering(false);
       setPressed(false);
     };
     const onMouseDown = () => setPressed(true);
     const onMouseUp = () => setPressed(false);
   -->

These event handlers will be used to manage our hovering / pressed state, which in turn will drive our behavior.

   -- CODE line-numbers language-javascript --
   <!--
     let className = "huggable";

     if (pressed) {
       className += " hugging-cursor";
     } else if (hovering) {
       className += " huggable-cursor";
     }
   -->

We set a `className` here dynamically based on our pressed / hovering state. This is used to control some basic styles as well as custom cursors when hovering. This might have been a little easier had I used JSS or styled components, but this served my needs just fine and will hopefully make sense to a wider audience.

   -- CODE line-numbers language-javascript --
   <!--
     return (
         <animated.div
           className={className}
           onMouseEnter={onMouseEnter}
           onMouseLeave={onMouseLeave}
           onMouseDown={onMouseDown}
           onMouseUp={onMouseUp}
           style={animationProps}
     role="button"
         >
           Hug me!
         </animated.div>
       );
   -->

Finally, our markup. Not much to see here as we're just passing down the props we defined above, but it's worth pointing out the `animated` tag, which is required by react-spring.

What we've got so far

![huggable animation in action](https://assets-global.website-files.com/5d7678efce0c5793e8bc3be5/620be0e83939f960e15bde40_custom-react-hook-example-animated.gif)

Not bad! Now let's try and isolate what we want to encapsulate in a hook. We know this should be applicable to any element, so we won't want to use any of the markup.

That leaves the state management, event handlers, the animation, and our classes.

   -- CODE line-numbers language-javascript --
   <!--
     const [hovering, setHovering] = useState(false);
     const [pressed, setPressed] = useState(false);
     const animationProps = useSpring({
       transform: `scale(${pressed ? 0.8 : 1})`
     });
     const onMouseEnter = () => setHovering(true);
     const onMouseLeave = () => {
       setHovering(false);
       setPressed(false);
     };
     const onMouseDown = () => setPressed(true);
     const onMouseUp = () => setPressed(false);

     let className = "huggable";

     if (pressed) {
       className += " hugging-cursor";
     } else if (hovering) {
       className += " huggable-cursor";
     }
   -->

If we copy that into its own function it looks something like this:

   -- CODE line-numbers language-javascript --
   <!--
     const useHug = () => {
       const [hovering, setHovering] = useState(false);
       const [pressed, setPressed] = useState(false);
       const style = useSpring({
         transform: `scale(${pressed ? 0.8 : 1})`
       });
       const onMouseEnter = () => setHovering(true);
       const onMouseLeave = () => {
         setHovering(false);
         setPressed(false);
       };
       const onMouseDown = () => setPressed(true);
       const onMouseUp = () => setPressed(false);

       let className = "";

       if (pressed) {
         className += "hugging-cursor";
       } else if (hovering) {
         className += "huggable-cursor";
       }

       //TODO: return...?
     };
   -->

What we want to return

All that's left now is what we want to return. This is an important decision as it defines what consuming components can do with our hook. In this case, I really want a consumer to be able to import the hook as one object and spread it over an html element, like so:

   -- CODE line-numbers language-javascript --
   <!--
     const huggableProps = useHug();

     return <a href="/contact" {...huggableProps}>Contact Us</a>
   -->

This makes our hook easy to consume and use while keeping some flexibility in case an element wants to pick and choose what events to use. In order to do that we have to leave off our state variables, since they aren't valid properties for html elements.

This is what our return statement winds up looking like:

   -- CODE line-numbers language-javascript --
   <!--
     return {
       onMouseDown,
       onMouseEnter,
       onMouseLeave,
       onMouseUp,
       className,
       style
     };
   -->

Now that we've got our hook, the only thing left to do is to use it:

   -- CODE line-numbers language-javascript --
   <!--
     export default function App() {
       const { className, ...hugProps } = useHug();
       const buttonHugProps = useHug();
       return (
         <div className="App">
           <animated.section className={`huggable ${className}`} {...hugProps}>
             I like hugs!
           </animated.section>

           <br />
           <br />
           <animated.button {...buttonHugProps} type="button">
             buttons need hugs too
           </animated.button>
         </div>
       );
     }
   -->

In the above example we've implemented our `useHug` hook in two ways, by taking all of the props and spreading them out over an element, and another by separating out the `className` prop and using that to compose a css class with our consuming element's existing className. We also make use of the `animated` tag to ensure our app animates correctly with react-spring.

Key takeaways

Although this example may seem kind of silly, a lot of the process for extracting logic into a custom hook would remain the same, no matter what you're building. As you identify patterns in your code it's a good practice to look for ways you can abstract application logic or behavior in the same way you would abstract a common UI element like a modal or input. This approach can help set you up for success as your application grows over time and prevent future developers (or future you) from reinventing the wheel on something you've already implemented a few times.

Access on CodeSandbox

If you'd like to see the full code, [here it is on codesandbox](https://codesandbox.io/s/huggable-qt088?file=/src/App.js).

Feel free to fork it and play around, I'd love to see what you come up with!

More React resources

Building SVG Line Charts in React

Intro to React Testing with Jest and React Testing Library

React: Optimize Components with React.memo, useMemo, and useCallback

Intro to Styling in React JavaScript

React Prototyping with Apollo Client Schemas

Enjoyed this post?

Other related posts

See all the ways we can help you grow through design, development, marketing, and more.

View All

Listen and learn from anywhere

Listen and learn from anywhere

Listen and learn from anywhere

The Manifest

Level up your skills and develop a startup mindset.
Stay up to date with the latest content from the Headway team.