Brief Intro to Hooks In React
For those of you who know or are learning React, you likely already know about React hooks. We use the useState
hook in order to create variables that trigger a re-render of the component should they be changed. We use the useEffect
hook to run code after the initial render, often to fetch data from an API and then trigger a re-render once the data is received. And there many more. But you may not know that you can create custom hooks, or it may not be clear when a custom hook would be useful. Hopefully this article helps you understand some good cases to use a custom hook. First, I'm going to go over how hooks work to help us to better understand how to create our own, then we can get to the examples.
How Hooks Work
Hooks work by taking advantage of the concept of closure. Closure is a feature of JavaScript that allows functions to remember and access their surrounding scope (variables declared outside the function) even when they are called outside of that scope. Closures can be confusing to understand at first. If you aren't quite sure what closure is and the above doesn't help, please check out this source which describes closure with an analogy of taking a photo, which could really help make it click. Also, this one that goes over stale closure and how to avoid it. This source also goes over creating our own implementations of existing React hooks in depth, so we can see closure in action in the context of React hooks specifically.
Hooks use closure in a few different ways, depending on the hook. For example, the useState hook works by creating stateful variables and state update functions. When the state update function is called, it has access to the current state value through closures, which allows it to update the state correctly even though it's outside of the component's local scope. The reason we pass a function to hooks like useEffect
is to create a closure of the necessary variables in the local component scope that can then be used in the scope of the useEffect
hook. While they may seem similar, Props passed from a parent to a child component do not directly use closure. However, it is sometimes the case that we define a function in the parents scope that references local variables and then pass that function to a child component as a callback. In this case, the child then has access to any of the parents variables from within the function. In this case, Props do take advantage of closure.
It is important to mention that while closure is an important feature in most hooks, our custom hooks can call other hooks- that means that instead of creating our own closure around variables and functions, we can also call useState
in our custom hook to create stateful variables for us. Also, if we are considering code for a custom hook and it has any JSX in it, that code is not suited for a custom hook and should remain as a component.
Another important feature about hooks to remember is that they must always be called in the same order, or they won't work. That is why you must place calls to a hook in the top-level of the component, and not inside any conditionals or statements. If one call is inside a conditional that no longer executes after the first render, React will still be expecting that call and assign it to the next call instead, creating errors.
Custom Hooks
Now that we have a better idea of what makes React hooks work, you may be asking when and why we might want to create our own custom hooks. As for why to create them, custom hooks are great for logic that we use multiple times throughout our components. By moving that logic to a custom hook, we can reuse the logic instead of writing it out each time we need it, reducing the length of our components and improving their readability. However, even if that logic isn't reused multiple times, the code may still be a candidate for being a hook if removing that logic from a component makes that component easier to read and understand.
When creating a custom hook, we must follow a rule in React for naming them: they must all begin with "use", such as in the first example, useQuery
below. This lets React know that the component should be treated as a hook and follow hook rules.
The following examples demonstrate great cases of when to use them, I hope they come in handy and inspire you to find other ways of using custom hooks in your own projects!
Examples
The examples I will cover in this article will provide a basic understanding of the logic behind each type of hook. However, you may want to make additional adjustments to tailor each hook to your specific needs. I will also mention potential enhancements for each example to help you get started. If you have any suggestions on how to further improve these examples, please feel free to leave a comment!
useQuery
One common potential use case for a custom hook is to separate out our logic for fetching data into its own hook. Here's how our useQuery
hook might look:
import { useState, useEffect } from "react";
function useQuery(url) {
const [data, setData] = useState([]);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then((data) => {
setData(data);
});
}, [url]);
return {data}
}
export default useQuery;
In our components, we could then simply call this hook with an argument for the URL to use in the fetch, and we'll get back our data. This saves us from having to write out a fetch request every time we need to get data from a server throughout our components. This example isn't perfect, but it shows the general idea. To improve it, we could add handling of errors with .catch
, cache our fetched data to prevent unnecessary network requests, or setup a canceling of the fetch should the component un-mount before the fetch is complete.
useTitle
Often, when a user navigates to a new page in our React app, we want to change the title displayed in the tab. While this is a simple to create in each component that needs it, it's a good candidate for being its own hook. Here's how we could make it as a custom hook:
import {useEffect} from "react";
function useTitle(title) {
useEffect(() => {
document.title = title;
}, [title]);
}
export default useTitle;
Pretty straightforward, I hope. The next one I'll talk about is a hook for keeping track of the mouse position within the browser window.
useMousePosition
Sometimes it's useful to know the mouse position, such as when placing tooltips relative to the mouse position, or drag and drop functionality. The mouse position is also important for zooming in and out on a specific area of interest, or when creating drawing applications, and many other situations. Here's a custom hook to keep track of its position, and it will update as that position changes:
import { useState, useEffect } from 'react';
function useMousePosition() {
const [mousePosition, setMousePosition] = useState({ x: null, y: null });
useEffect(() => {
function handleMouseMove(event) {
setMousePosition({ x: event.clientX, y: event.clientY });
}
document.addEventListener('mousemove', handleMouseMove);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
};
}, []);
return mousePosition;
}
Using this custom hook to keep track of mouse position saves us from having to re-write the same logic over and over as well as reducing clutter in our components.
useToggle
import { useState, useCallback } from "react";
const useToggle = (initial) => {
const [open, setOpen] = useState(initial);
return [open, useCallback(() => setOpen(status => !status), [])];
};
export default useToggle;
In this example, for any components that use useToggle
, we return a bool of whether the toggle is open or not, as well as a function to set the status of the toggle to open or closed depending on its state at the time. Notice we wrapped the returned function in a useCallback
hook. The useCallback
hook memoizes the callback function, and only re-creates the function if any dependencies specified in the dependency array have changed. In this case, we have no dependencies, so we provide an empty array.
However, React ensures that the value of status
in our setter function is always the current value for open
, due to us passing a function to setOpen
rather than directly providing a value. This is called a functional state update, and you can read more about how it works here. And for more information on memoization and how it works, check out this helpful article.
Wrapping Things Up
I hope my overview of how hooks worked and the examples I provided helped you in understanding both hooks overall as well as how and when custom hooks can come in handy. There are many, many more uses for custom hooks- the possibilities are endless! I highly recommend checking out some of the links below for some great, ready-made custom hooks that you can use in your projects. The examples I provided are really only the tip of the iceburg! If you're a beginner, some may seem too complicated and you may have no use for them at this point, but I highly recommend bookmarking the pages for later use!
Further reading:
For an extensive list of use cases for custom hooks, I recommend checking out streamich's GitHub page, where you can get a bunch of custom hooks already made! For even more, check out this and this.
For more information on hooks themselves, I recommend codersociety's post on the benefits of React hooks over class components.
Top comments (0)