Skip to content

feat: deferred queries in loader #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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