[SOLVED] Move a const/function in to a custom hook / re-usable piece of code

Issue

In my functional component / page I’ve got a const/function that I pass error messages to, that then updates a couple of useState records and logs the error to the console. It looks like this:

const catchError = (e:any, message:string) => {
    //throw new Error(e)
    setMessage(message);
    setLoading(false);
    console.error(e);
};

I’d like to move this code to a hook(?) / helper file that will allow me to import it into any other component. This would mean I can use one centralised piece of code rather than writing the same code in multiple places and avoid the associated issues.

So, I’ve created a new catchError.tsx file with the following content:

import { useState } from 'react';

const [ message, setMessage ] = useState("idle");
const [ loading, setLoading ] = useState(false);

export const catchError = (e:any, message:string) => {
    //throw new Error(e)
    setMessage(message);
    setLoading(false);
    console.error(e);
};

However, when I render this I get the following compile error:

Failed to compile

src/hooks/catchError.tsx Line 3:33: React Hook "useState" cannot be
called at the top level. React Hooks must be called in a React
function component or a custom React Hook function
react-hooks/rules-of-hooks Line 4:33: React Hook "useState" cannot
be called at the top level. React Hooks must be called in a React
function component or a custom React Hook function
react-hooks/rules-of-hooks

Search for the keywords to learn more about each error.

This is new territory for me, so I’m stumbling along trying to find a solution and would appreciate any input you could offer to resolve the issue.

Thanks.

(P.s. I’m quite new to react / TypeScript so apologies if my terminology is wrong. I welcome any corrections in this regard.)

Solution

If you want to reuse hook logic (like useState) you can write your own custom hooks.

I believe something like so could work, though I am not completely sure how this would be used without more context.

import { useState } from 'react';

function useCatchError() {
  const [ message, setMessage ] = useState("idle");
  const [ loading, setLoading ] = useState(false);

  const catchError = (e:any, message:string) => {
    //throw new Error(e)
    setMessage(message);
    setLoading(false);
    console.error(e);
  };

  return { catchError, message, loading };
}

And be used like

export function Foo() {
  const {catchError, message, loading} = useCatchError();
  
  try {
    bar();
  } catch (e) {
    catchError(e, e.message);
  }

  return (
    <div>
       {message ? `Error: ${message}` : loading ? "Loading" : "Foo"}
    </div>
  );
}

More on custom hooks in the React Docs: https://reactjs.org/docs/hooks-custom.html

Answered By – Antoine Gagnon

Answer Checked By – Dawn Plyler (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.