Skip to content

Instantly share code, notes, and snippets.

@ellemedit
Last active March 17, 2021 01:07
Show Gist options
  • Save ellemedit/b1434910e563181a049ba116e8c08650 to your computer and use it in GitHub Desktop.
Save ellemedit/b1434910e563181a049ba116e8c08650 to your computer and use it in GitHub Desktop.
suspenseAll: React Suspense hook sugar for fetching data concurrently.

suspenseAll

React Suspense hook sugar for fetching data concurrently.

Why this needed?

React suspense implementations is that catches a thrown promise in suspense tree. See useSWR and react-query. This blocks next hook till previous promise done if you don't prefetch.

suspenseAll allows us to work concurrently. Simply, you can think it's React hook version of Promise.all. This also obey the Rules of Hooks conceptullay.

I think this is not the answer for parallel work, but it's attempt to solve problem at this moment. I believe in React team will make better interface for Suspense.

Caveat

If you use ESLint and rules-of-hooks rule, there's no good options. You should write // eslint-disable-next-line react-hooks/rules-of-hooks or turn off the rule.

// tested in TS 3.8.2
type Returned<T> = T extends () => infer P ? P : T;
export const suspenseAll = <T extends ((...args: any[]) => any)[]>(...hooks: T): { [P in keyof T]: Returned<T[P]> } => {
const exceptions: Promise<any>[] = [];
const results: any[] = [];
for (const hook of hooks) {
try {
results.push(hook());
} catch (exception) {
// instanceof Promise not enough in some case
if ("then" in exception && typeof exception.then === "function") {
exceptions.push(exception);
} else {
throw exception;
}
}
}
if (hooks.length === results.length) {
return results as any;
}
throw Promise.all(exceptions);
};
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import { useQuery } from 'react-query';
const Component = () => {
const [a, b] = suspenseAll(
/* eslint-disable react-hooks/rules-of-hooks */
() => useQueryA(),
() => useQueryB(),
/* eslint-enable react-hooks/rules-of-hooks */
);
return <div>{a.data} and {b.data}</div>;
}
const useQueryA = () => useQuery('a', () => fetch('...'), { suspense: true });
const useQueryB = () => useQuery('b', () => fetch('...'), { suspense: true });
ReactDOM.render(
<Suspense>
<Component />
</Suspense>
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment