Search code examples
reactjsreact-hookses6-promise

Custom hook returns Promise but resolves inside useEffect


I'm trying to write a hook which performs a dynamic import and returns a Promise which resolves when the import has completed.

Here's how I envision it's usage:

await useAfterImport("@custom/path");

Here is my faulty attempt:

const useAfterImport = (importPath:string) => {
  return new Promise<void>((resolve) => {
    useEffect(() => {
      import(importPath).then(() => {
        resolve();
      });
    }, [importPath]);
  });
};

This attempt fails because the Rules of Hooks have been violated:

React Hook "useEffect" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function. eslintreact-hooks/rules-of-hooks

Is there a way that a hook like this can be written without resorting to using a callback parameter? I would really like to be able to return a Promise so it can be awaited.


Solution

  • I don't think Promises are the right way to go here:

    Here's how I envision it's usage:

    await useAfterCustomImport("@custom/path");
    

    The top level of React component function bodies - the only place hooks can be used - can't have await inside of them - they need to render something immediately (even if temporarily an empty fragment).

    For what you're trying to do, state makes more sense. You could do something like the following:

    const useAfterCustomImport = (importPath: string) => {
      const [imported, setImported] = useState(false);
      useEffect(() => {
        import(importPath)
          .then(() => setImported(true))
          // .catch(handleErrors); // don't forget this part
      }, []);
      return imported;
    };
    

    and then

    const libImported = useAfterCustomImport('some-lib');
    return !libImported ? null : (
      <div>
        // JSX that uses the library
      </div>
    ); 
    

    This follows the current logic of your current code, which appears to assume that the import contains only side-effects, rather than resolving to useable values. If the module resolves to a useable value, returning both imported and the resolve value from the custom hook would be a trivial tweak.