Skip to main content
Version: 4.1

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>
);
}