Skip to main content
Version: 4.3

Handling loading state

Network resources take an unknown amount of time so it's important to be able to handle rendering while they load. After sometime you might want to display a loading indicator, but at the very least you'll need to be able to deal with not having the resource available yet!

Normally you might do a check to see if the resource is loaded yet and manually specify each fallback condition in every component. However, since rest-hooks uses React's Suspense API, it is much simpler to do here.

Using Suspense#

Simply place the <Suspense /> component where you want to show a loading indicator. Often this will be just above your routes; but feel free to place it in multiple locations!

App.tsx#

import { Suspense } from 'react';import { NetworkErrorBoundary } from 'rest-hooks';import { RouteChildrenProps } from 'react-router';
const App = () => (  <div>    <h1>Main Title</h1>    <Nav />    <Suspense fallback={<Spinner />}>      <NetworkErrorBoundary>        <Routes />      </NetworkErrorBoundary>    </Suspense>  </div>);

Note:#

The <Suspense/> boundary must be placed in another component that is above the one where useResource() and other hooks are used.

Without Suspense#

Suspense is the recommended way of handling loading state as it reduces complexity while integrating with React.lazy() and the soon-to-be-released concurrent mode. However, if you find yourself wanting to handle loading state manually you can adapt the useStatefulResource() hook.

Loading Buttons#

When performing mutations you'll often want an indicator that the request is still in flight. Sometimes form libraries will handling the loading state themselves. However, in the case you're making a standalone button or simply using a form library that doesn't track loading state of submitters, you can use the following hook.

Hook#

/** Takes an async function and tracks resolution as a boolean. * */function useLoadingFunction<F extends Function>(  func: F,  onError?: (error: Error) => void,): [F, boolean] {  const [loading, setLoading] = useState(false);  const isMountedRef = useRef(true);  useEffect(    () => () => {      isMountedRef.current = false;    },    [],  );  const wrappedClick = useCallback(    async (...args: any[]) => {      setLoading(true);      try {        const ret = await func(...args);      } catch (e) {        if (onError) onError(e);        else throw e;      }      if (isMountedRef.current) setLoading(false);      return ret;    },    [onError, func],  );  return [wrappedClick, loading];}

Usage#

function Button({ onClick, children, ...props }) {  const [clicker, loading] = useLoadingFunction(onClick);  return (    <button onClick={clicker} {...props}>      {loading ? 'Loading...' : children}    </button>  );}