How to write custom hooks in React

Hooks have been the latest hotness in React for a while now. They come with certain advantages like reducing the need for render props (god thank you!) and being able to have state in function based components. If you haven’t used them yet, I really recommend you check out the docs before reading on.

Custom hooks allow you to create functionality that can be reused across different components. You can of course just have functions to reuse functionality, but hooks come with the advantage of being able to ‘hook’ into things like component lifecycle and state. This makes them much more valuable in the React world than regular functions.

What makes a custom hook? A custom hook is just a function that uses other hooks. If you don’t use any hooks in your function, it’s just a function, not a hook. By convention, the name of a hook function should start with ‘use’. It doesn’t have to, but if it doesn’t people won’t easily realise it’s a hook.

To show you an example of a custom hook I’m going to code a simple hook that could actually be useful in the real world.

We’ll call our hook useBodyScrollPosition.

The idea is that each time the body of the document is scrolled, the hook will fire and return the current scroll offset. This could be useful in such cases where you need to  move a component on the page or change it in some way in response to scrolling.

Of course you could do this just within your component, but then it wouldn’t be reusable in other components, which is one of the main advantages of hooks.

So without further ado, here’s our component:  

// use-body-scroll-position.js
import { useState, useEffect } from 'react';

export default () => {
  const [scrollPosition, setScrollPosition] = useState(null);
  useEffect(() => {
    const handleScroll = () => setScrollPosition(window.scrollY);
    document.addEventListener('scroll', handleScroll);
    return () => 
      document.removeEventListener('scroll', handleScroll);
  }, []);
  return scrollPosition;
}

useEffect makes sure the event listener gets setup when the hook is mounted. The function returned by useEffect’s function will be called when the hook is unmounted, and this will clean up by removing the event listener. If we don’t do this it will try and set state on an unmounted hook when it fires.

The second argument to useEffect, an empty array of dependencies, ensures that the effect is only called once, when the hook first mounts. We don’t want to keep adding the event listener!

The state is just a single value, the scroll offset, and this is the value our hook returns. It defaults to null, which will always be returned when the hook is first called. This value will only change when there’s a scroll event, it will remain null till then. Changes would be required if you wanted it to return the current offset prior to any scroll.

Each time the scroll event fires, the state updates, the hook function gets called again with the latest state, and returns the scroll offset to the calling component.

Here’s an example of using it in a component. All it does is put the scroll value in the middle of the window, updating as you vertically scroll. I wouldn’t usually use inline styles, but wanted it all in one file for this post.

import React from 'react';
import useBodyScrollPosition from './use-body-scroll-position';

export default () => {
  const scrollPosition = useBodyScrollPosition();
  const wrapperStyles = {
    height: '5000px',
  };
  const displayStyles = {
    position: 'fixed',
    width: '100%',
    top: '50%',
    transform: 'translateY(-50%)',
    fontSize: '20px',
    textAlign: 'center',
  }
  return (
    <div style={wrapperStyles}>
      <div style={displayStyles}>
        {scrollPosition !== null ? scrollPosition : 0}
      </div>
    </div>
  )
}

Here’s that component in action below.

Of course the hook is maybe not quite production ready. You’d probably want to add configuration options for performance optimisation, like debouncing or only firing based on a predicate on the scroll position, but this is about creating custom hooks so I didn’t bother with all that.

Overall take away, creating custom hooks is easy!

Leave a Reply

Your email address will not be published. Required fields are marked *