With the introduction of hooks in React, the separation of React component display and the component State can be separated while still keeping the methodologies of function components. Here are a few of my tips, tricks, and rules of thumb for custom hooks.

When do I create a custom hook vs keep my state in my component?

This is probably one of the first questions I started asking myself when I started learning custom hooks. The React Docs for Custom Hooks makes multiple references to extracting logic to a reusable component. Although reuse should be a priority, there are cases where using a custom hook is beneficial even when reuse in another React component is unlikely. Generally I look for the following as an indication of when to create a custom hook.

  • When your component triggers an async function that needs to updates the state outside of an initial useEffect
  • If you begin to create functions that perform updates to the state outside of a useEffect, such as filter functions or complex onClick functions
  • If my useState and useEffect block is starting to clutter my component or get overly complex

Should I use a single useState or multiple in my custom hook?

It is safe to use as many useState commands as you need in a custom hook as long as the standard useState rules are followed. In my experience it is best to use fewer useStates, but in some cases it is optimal to use more than 1. I generally split state up based on what it is intended to hold. For example, if my single hook manages the data, selected values, and display properties (size selection, page theme, etc) I would use two useState calls, one with data and selected values, and the other with display properties.

How to implement useRef to keep your hook async safe

For most of my projects this has become super critical. When using async functions you may see stale state, this has been outlined in the React Hooks Faq here. For most of my hooks that I need to be async I use a basic implementation like this.

function useAsyncSafeState(){
  const [state, setState] = useState(null);
  const stateRef = useRef(state);
  
  const seStateWithRef = (newState)=>{
    stateRef.current = newState;
    return setState(newState);
  };
  
  return [stateRef.current, seStateWithRef];
}

What not to do in your hook....

Avoid using any useState item as an Effect second argument

Using an item of your state as an argument for determining when an Effect should run usually causes endless re rendering of your React component and re-execution of the effect.

If you find you need to run two setState methods at the same time, you should combine them to a single setState

When using multiple states, avoid calling two setStates within a single function. This can cause some odd behavior such as state changes not being applied to one of the setStates called. Below is an example of what not to do, along with a fix.

/*
Avoid doing the double setState call 
*/
function useMyHook(){
  const [title, setTitle] = useState("My Title");
  const [contents, setContents] = useState("Contents");
  
  const updateData = (title, contents) => {
    // this is bad
    setTitle(title);
    setContents(contents);
  }
  
  return [title, contents, updateData];
}

/*
If you need to do a double set call, then combine the states like this
*/
function useMyHook(){
  const [state, setState] = useState({
    title: "My Title",
    contents: "Contents"
  });
  
  const updateData = (title, contents) => {
    // this is bad
    setState({
      title: title,
      contents: contents
    })
  }
  
  return [state.title, state.contents, updateData];
  /*
  or you could return [state, updateData]...
  */
}