DEV Community

jhalfman
jhalfman

Posted on

Custom React Hooks

Where did Hooks come from?

Initially, React relied on class components for things like state and sharing logic between components, causing complicated component hierarchies and confusing prop logic. To alleviate these issues, React introduced hooks at the end of 2018. There are some very common hooks built in, such as useState and useEffect, but React also provides the functionality to create one's own custom hooks.

A break from class

In order to prevent the need for classes, hooks allow the user to "hook" into React state from your React function components. Hooks always start with "use" and follow two main functional rules. Hooks should not be called from inside loops or conditionals; they should be kept at the top level. They should also only be called from React function components, not regular JS functions. The only other acceptable place to call a hook is from a custom hook component.

Build your own

As mentioned, the convention for a custom hook is "useSOMETHING." Any time you want to reuse stateful logic between multiple components, you have a good opportunity to create a custom hook. To start, create a file for our new hook just like for a function component, something like src/hooks/useBlogExample.js. Creating the function looks just like a normal component, except it's called "use____" instead of the normal capitalization of the first letter.

function useBlogExample() {
  //DO SOMETHING HERE
}

export default useBlogExample;
Enter fullscreen mode Exit fullscreen mode

Our custom hook can provide a simple action that any function could do, like simply printing something to the console.

import { useState, useEffect } from 'react';

function useBlogExample() {
  console.log("THIS IS FROM A CUSTOM HOOK")
}

export default useBlogExample;
Enter fullscreen mode Exit fullscreen mode

To call this hook, we would just have to import it and invoke it just like any other function or hook useBlogExample(). This isn't very useful, though, since a console.log doesn't need a hook to utilize it. Remember, custom hooks are the one other place that hooks can be called aside from React function components, so let's write a hook that utilizes useState and useEffect. To start, we can import useState and useEffect into our custom hook.

import { useState, useEffect } from 'react';

function useBlogExample() {
  //DO SOMETHING HERE
}

export default useBlogExample;
Enter fullscreen mode Exit fullscreen mode

Within the custom hook, state and useEffect work the same way as in a React component. We can set state within the hook, and we can call useEffect for something like an API fetch.

import { useState, useEffect } from 'react';

function useBlogExample() {
  const [state, setState] = useState(null);

  useEffect(() => {
    fetch("http://localhost:3000/items")
    .then(resp => resp.json())
    .then(data => setState(data))
  }, []);

export default useBlogExample;
Enter fullscreen mode Exit fullscreen mode

As it is currently, this hook will only adjust its own state to the data returned from the API fetch. If we want data to actually come out of this hook (and we do!), we need to simply return the data, just like from a regular function.

import { useState, useEffect } from 'react';

function useBlogExample() {
  const [state, setState] = useState(null);

  useEffect(() => {
    fetch("http://localhost:3000/items")
    .then(resp => resp.json())
    .then(data => setState(data))
  }, []);

  return {state: state};

export default useBlogExample;
Enter fullscreen mode Exit fullscreen mode

We return an object with the state variable so that it can be destructured when called in a component. const {data} = useBlogExample();
Now, every time we call useBlogExample, the hook will return for use the results of that API call. This is useful, but is likely not very versatile. It is not likely that two components would be making the exact same fetch call. Perhaps two components are making fetch calls to two locations in the database. We can adapt our hook to be accepting of varying urls! We just have to allow the hook to accept a parameter, and utilize the parameter in the url of the fetch call.

import { useState, useEffect } from 'react';

function useBlogExample(location) {
  const [state, setState] = useState(null);

  useEffect(() => {
    fetch(`http://localhost:3000/${location}`)
    .then(resp => resp.json())
    .then(data => setState(data))
  }, []);

  return {state: state};

export default useBlogExample;
Enter fullscreen mode Exit fullscreen mode

Now, one component could call our hook to get a response from one endpoint, and a separate hook could make a call to a different location! Perhaps we want to make calls from localhost:3000/names and from /professions. In one component, we could call {state} = useBlogExample("names") and from the other component {state} = useBlogExample("professions"). If using a hook to return differing types of data, make sure to keep the variable name vague enough to describe both possibilities. If you'd like, you can rename the data to a more specific variable when you destructure it.

And that's it! Custom hooks are essentially just reusable functions, but they can utilize state and other hooks to clean up some redundancy among your React components. They have the added bonus of being used by any new components that you create as your application grows.

Top comments (0)