DEV Community

Katherine Kelly
Katherine Kelly

Posted on

Getting Acquainted with React Custom Hooks

I previously wrote an introductory post about React Hooks called Playing Hooky with React that explored useState and useEffect, eschewing the need for class components. I also wrote a follow up Playing More Hooky with React exploring why I prefer Hooks going forward for any React or React Native projects.

As projects get more complex and stateful logic is being used among different components, custom Hooks can come to your rescue. As my blog title indicates, I want to take a deeper dive into the whys and hows of custom Hooks so follow along with me!

Why Use A Custom Hook

Custom Hooks are useful for when you want to share stateful logic between components. Keep in mind that state itself is not shared between these components, as state in each call to a Hook is completely independent. That means you can use the same custom Hook more than once in a given component.

In the past, the most common ways to share stateful logic between components was with render props and higher-order components. Now with custom Hooks, it solves this problem without adding more components to your tree.

Custom Hooks can cover a wide range of use cases like form handling, animation, mouse and scroll events, timers, and lots more. Along with separating out related logic in your components, custom Hooks can help conceal complex logic behind a simple interface.

An Example of Using a Custom Hook

An example, albeit a contrived one, of when it's useful to extract stateful logic into a custom Hook is if you want to show your user an indicator of how far they've scrolled on a page or progress read in an article. That logic could live in its own custom Hook and be reused in the components where you want to show a progress meter or percentage showing the progress via scrolling (like a home page or article component).

Below is an example Article component that gets the window's scroll position in order to show the progress made via a progress meter.

import React, { useState, useEffect } from 'react';
import ProgressMeter from './ProgressMeter';

function Article() {
  const [scrollPosition, setScrollPosition] = useState(null);

  useEffect(() => {
    function handleWindowScrollPosition(e) {
      setScrollPosition(window.scrollY);
    }
    window.addEventListener('scroll', handleWindowScrollPosition);
    return () => window.removeEventListener('scroll', handleWindowMouseMove);
  }, []);

  return (
    <div>
      <ProgressMeter scrollPosition={scrollPosition} />
    // .. code here for sweet article render
    </div>
  )
}

How to Build Your Own Hook

On the surface, a custom Hook is just like a typical JavaScript function. But there are some conventions that turn a normal function into your brand spanking new custom Hook, such as naming your function to start with use and the ability to call other Hooks.

You can think of these conventions as governed by a set of rules. The React docs indicate that the rules of Hooks are enforced by an ESLint plugin that React provides. The rules are:

1. Only call Hooks from React functions

  • call Hooks from React function components
  • call Hooks from custom Hooks

2. Only call Hooks at the top level of your function

  • never call a Hook inside loops, nested functions, or conditions
Side Note on ESLint Plugin

The ESLint plugin that enforces the Hook rules is eslint-plugin-react-hooks. If you create your project using create-react-app the plugin will be included by default. Otherwise, you can add it to your project with:

npm install eslint-plugin-react-hooks --save-dev

Name Starts with use

It's convention to name your Hook starting with use. And as you may tell where this is going, the ESLint plugin will assume that a function starting with "use" and a capital letter immediately after is a Hook. Repeat after me, always start your custom Hook's name with use!

function useWindowScrollPosition() {
// ...
}

Calling Other Hooks

Though you may be wondering, "Couldn't I just have a regular JavaScript function that would have that functionality instead of building my own Hook?", the answer is sure you can, but then you would not have access to Hooks within that function. Per the rules of React, there are only two places where you can call a Hook: a React function component and from a custom Hook.

When calling other Hooks in your custom Hook, or even in a React function component, you want to keep it at the top level of the component. This will ensure that the order of Hooks being called remain in order.

Below, I've extracted the stateful logic from the above Article component into a custom Hook for reuse in other components.

// useWindowScrollPosition.js
import React, { useState, useEffect } from 'react';

export default function useWindowScrollPosition() {
  const [scrollPosition, setScrollPosition] = useState(null);

  useEffect(() => {
    function handleWindowScrollPosition(e) {
      setScrollPosition(window.scrollY);
    }
    window.addEventListener('scroll', handleWindowScrollPosition);
    return () => window.removeEventListener('scroll', handleWindowMouseMove);
  }, []);
  return scrollPosition;
}

Using Your Custom Hook

Now that I've built my custom Hook, it's easy to use it. You just call it and can save it as a variable in your components.

import React from 'react';
import useWindowScrollPosition from './useWindowScrollPosition';
import ProgressMeter from './ProgressMeter';

function Article() {
  const position = useWindowScrollPosition();

  return (
    <div>
      <ProgressMeter position={position} />
      // .. code here for sweet article render
    </div>
  )
}

React Hooks, whether built-in or custom, are a great addition to making your components more reusable and composable. Happy coding!

Resources
React - Building Your Own Hooks

Top comments (2)

Collapse
 
siradji profile image
Suraj Auwal • Edited

Nice article Katherine. I was just about to write a post on custom hooks and I can see you beat me to it. Keep it up!

Collapse
 
katkelly profile image
Katherine Kelly

Thanks! Glad you found it helpful!