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!
Liked this? Then you'll love my mailing list. I have a regular newsletter on JavaScript, tech and careers. Join over 5,000 people who enjoy reading it. Signup to my list here.
Top comments (5)
Very helpful article! Thanks.
I have a question related to this paragraph:
When the hook re-runs and returns the scroll offset to the calling component. How's the component picking up the new change? The re-run/re-render happened inside the hook only in this case.
By design, React uses memory cells to store the state for the component instance. Does this apply to custom hooks too? If yes, then it makes sense. The custom hook will return a value, the scrollPosition local state changes inside the corresponding memory cell, this causes the component to trigger a self re-render.
Thank you
Bilal Haidar
"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."
Are you sure about this? I tried changing one of my functions name from useBlah to getBlah and it broke.
now reactjs.or says: "Its name should always start with use so that you can tell at a glance that the rules of Hooks apply to it."
Thank you for the useful article. I had a confusion to what exactly defines a hook function but that's all cleared up now. Hooks are, by far, my favorite feature of React!
Thanks for this no-nonsense article that got me writing my first custom hook :)