Skip to content

Commit

Permalink
feat: deferred queries in loader
Browse files Browse the repository at this point in the history
  • Loading branch information
imp-dance committed Jan 18, 2023
1 parent 5947fda commit 50fcdf7
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 9 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,35 @@ const Component = () => {
};
```

## Deferring queries

You can defer queries by using the `deferredQueries` argument in `createLoader` (or `createUseLoader`). These queries are passed as the second argument to `transform` which has to be used to access the deferred queries in your loaded component.

Example usage:

```tsx
const loader = createLoader({
queries: () => [useImportantQuery()] as const,
deferredQueries: () => [useSlowButNotImportantQuery()] as const,
transform: (queries, deferredQueries) => ({
important: queries[0].data,
not_important: deferredQueries[0].data,
}),
});

const Component = withLoader((props, loaderData) => {
const { important, not_important } = loaderData;
// not_important could be undefined

return (
<div>
{important.person.name}
{not_important ? "it has resolved : "some fallback"}
</div>
)
}, loader);
```
## InferLoaderData
Infers the type of the data the loader returns. Use:
Expand Down
23 changes: 23 additions & 0 deletions deploy.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env zx
try {
await Promise.all([
$`yarn build`,
$`npm version patch --force`,
]);
const version = require("./package.json").version;
await Promise.all([
$`git commit --allow-empty -m "version: ${version}"`,
$`git push`,
$`npm publish --access public`,
$`echo "======================"`,
$`echo "Deployed! 🚀 (${version})"`,
$`echo "======================"`,
]);
} catch (err) {
await Promise.all([
$`echo "======================"`,
$`echo "Deploy failed! 😭"`,
$`echo "======================"`,
$`echo "${err}"`,
]);
}
17 changes: 13 additions & 4 deletions src/createLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ import * as Types from "./types";

export const createUseLoader = <
QRU extends readonly Types.UseQueryResult<unknown>[],
QRUD extends readonly Types.UseQueryResult<unknown>[],
R extends unknown = Types.MakeDataRequired<QRU>,
A = never
>(
createUseLoaderArgs: Types.CreateUseLoaderArgs<QRU, R, A>
createUseLoaderArgs: Types.CreateUseLoaderArgs<QRU, QRUD, R, A>
): Types.UseLoader<A, R> => {
const useLoader = (...args: Types.OptionalGenericArg<A>) => {
const createdQueries = createUseLoaderArgs.queries(...args);
const deferredQueries =
createUseLoaderArgs.deferredQueries?.(...args) ?? [];
const aggregatedQuery = aggregateToQuery(createdQueries);

if (aggregatedQuery.isSuccess) {
const data = createUseLoaderArgs.transform
? createUseLoaderArgs.transform(
createdQueries as unknown as Types.MakeDataRequired<QRU>
createdQueries as unknown as Types.MakeDataRequired<QRU>,
deferredQueries as QRUD
)
: createdQueries;

Expand All @@ -36,15 +40,17 @@ export const createUseLoader = <
export const createLoader = <
P extends unknown,
QRU extends readonly Types.UseQueryResult<unknown>[] = [],
QRUD extends readonly Types.UseQueryResult<unknown>[] = [],
R extends unknown = Types.MakeDataRequired<QRU>,
A = never
>(
createLoaderArgs: Types.CreateLoaderArgs<P, QRU, R, A>
createLoaderArgs: Types.CreateLoaderArgs<P, QRU, QRUD, R, A>
): Types.Loader<P, R, QRU, A> => {
const useLoader = createUseLoader({
queries:
createLoaderArgs.queries ?? (() => [] as unknown as QRU),
transform: createLoaderArgs.transform,
deferredQueries: createLoaderArgs.deferredQueries,
});

const loader: Types.Loader<P, R, QRU, A> = {
Expand All @@ -58,6 +64,7 @@ export const createLoader = <
createLoaderArgs.loaderComponent ?? RTKLoader,
extend: function <
QRUb extends readonly Types.UseQueryResult<unknown>[],
QRUDb extends readonly Types.UseQueryResult<unknown>[],
Pb extends unknown = P,
Rb = QRUb extends unknown
? R
Expand All @@ -67,7 +74,9 @@ export const createLoader = <
queries,
transform,
...loaderArgs
}: Partial<Types.CreateLoaderArgs<Pb, QRUb, Rb, Ab>>) {
}: Partial<
Types.CreateLoaderArgs<Pb, QRUb, QRUDb, Rb, Ab>
>) {
const extendedLoader = {
...(this as unknown as Types.Loader<Pb, Rb, QRUb, Ab>),
...loaderArgs,
Expand Down
22 changes: 18 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ export type OptionalGenericArg<T> = T extends never ? [] : [T];

export type LoaderTransformFunction<
QRU extends readonly UseQueryResult<unknown>[],
QRUD extends readonly UseQueryResult<unknown>[],
R extends unknown
> = (queries: MakeDataRequired<QRU>) => R;
> = (queries: MakeDataRequired<QRU>, deferredQueries: QRUD) => R;

export type CreateUseLoaderArgs<
QRU extends readonly UseQueryResult<unknown>[],
QRUD extends readonly UseQueryResult<unknown>[],
R extends unknown,
A = never
> = {
Expand All @@ -70,8 +72,18 @@ export type CreateUseLoaderArgs<
* ```
*/
queries: (...args: OptionalGenericArg<A>) => QRU;
/** Should return a list of RTK useQuery results.
* Example:
* ```typescript
* (args: Args) => [
* useGetPokemonQuery(args.pokemonId),
* useGetSomethingElse(args.someArg)
* ] as const
* ```
*/
deferredQueries?: (...args: OptionalGenericArg<A>) => QRUD;
/** Transforms the output of the queries */
transform?: LoaderTransformFunction<QRU, R>;
transform?: LoaderTransformFunction<QRU, QRUD, R>;
};

export type UseLoader<A, R> = (
Expand Down Expand Up @@ -136,9 +148,10 @@ export type CustomLoaderProps<T = unknown> = {
export type CreateLoaderArgs<
P extends unknown,
QRU extends readonly UseQueryResult<unknown>[],
QRUD extends readonly UseQueryResult<unknown>[],
R extends unknown = MakeDataRequired<QRU>,
A = never
> = Partial<CreateUseLoaderArgs<QRU, R, A>> & {
> = Partial<CreateUseLoaderArgs<QRU, QRUD, R, A>> & {
/** Generates an argument for the `queries` based on component props */
queriesArg?: (props: P) => A;
/** Determines what to render while loading (with no data to fallback on) */
Expand Down Expand Up @@ -188,6 +201,7 @@ export type Loader<
/** Returns a new `Loader` extended from this `Loader`, with given overrides. */
extend: <
QRUb extends readonly UseQueryResult<unknown>[] = QRU,
QRUDb extends readonly UseQueryResult<unknown>[] = [],
Pb extends unknown = P,
Rb extends unknown = QRUb extends QRU
? R extends never
Expand All @@ -196,7 +210,7 @@ export type Loader<
: MakeDataRequired<QRUb>,
Ab = A
>(
newLoader: Partial<CreateLoaderArgs<Pb, QRUb, Rb, Ab>>
newLoader: Partial<CreateLoaderArgs<Pb, QRUb, QRUDb, Rb, Ab>>
) => Loader<Pb, Rb, QRUb extends never ? QRU : QRUb, Ab>;
/** The component to use to switch between rendering the different query states. */
LoaderComponent: Component<CustomLoaderProps>;
Expand Down
6 changes: 5 additions & 1 deletion testing-app/src/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ export const handlers = [
if (req.params.name === "error") {
return res(c.delay(RESPONSE_DELAY), c.status(500));
}
const delay =
req.params.name === "delay"
? RESPONSE_DELAY + 100
: RESPONSE_DELAY;
return res(
c.delay(RESPONSE_DELAY),
c.delay(delay),
c.status(200),
c.json({
name: req.params.name,
Expand Down
38 changes: 38 additions & 0 deletions testing-app/src/tests.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,44 @@ describe("withLoader", () => {
);
});

test("Can defer some queries", async () => {
const Component = withLoader(
(props, { charizard, delay }) => {
return (
<>
<div>{charizard.name}</div>
<div>
{delay ? "loaded-deferred" : "loading-deferred"}
</div>
</>
);
},
createLoader({
queries: () =>
[useGetPokemonByNameQuery("charizard")] as const,
deferredQueries: () => {
const delayQ = useGetPokemonByNameQuery("delay");
return [delayQ] as const;
},
transform: (queries, deferred) => ({
charizard: queries[0].data,
delay: deferred[0].data,
}),
onLoading: () => <div>Loading</div>,
onError: () => <div>Error</div>,
})
);
render(<Component />);
expect(screen.getByText("Loading")).toBeVisible();
await waitFor(() =>
expect(screen.getByText("charizard")).toBeVisible()
);
expect(screen.getByText("loading-deferred")).toBeVisible();
await waitFor(() =>
expect(screen.getByText("loaded-deferred")).toBeVisible()
);
});

describe(".extend()", () => {
test("Can extend onLoading", async () => {
render(<ExtendedLoaderComponent />);
Expand Down

0 comments on commit 50fcdf7

Please sign in to comment.