Skip to content

Commit

Permalink
version: 1.0.4
Browse files Browse the repository at this point in the history
  • Loading branch information
imp-dance committed Jul 1, 2023
1 parent af61485 commit 47f95dd
Show file tree
Hide file tree
Showing 12 changed files with 354 additions and 43 deletions.
14 changes: 14 additions & 0 deletions docs/docs/Quick Guide/accept-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,17 @@ type ConsumerProps<T extends Record<string, unknown>> = Record<
// → "1234"
// → loader.useQueries("1234")
```

## When using `<Load />`

You should pass the expected _props_ to the `arg` prop when using `<Load />`.

```tsx
<Load
loader={loader}
render={(data) => (...)}
args={{
userId: "1234",
}}
/>
```
56 changes: 53 additions & 3 deletions docs/docs/Quick Guide/consume-loader.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ import TabItem from "@theme/TabItem";

# 5. Consume the loader

You can consume a loader using one of the following methods:

<Tabs groupId="method" queryString>
<Tab label="Using withLoader" value="withLoader" default>
<Tab label="withLoader" value="withLoader" default>

A convenient wrapper that ensures that the component is only rendered when it has data.

This is definitely the preferred and optimal way to consume the loaders.
:::info
This is the recommended and optimal way to consume the loaders.
:::

```tsx {8-26}
import { withLoader } from "@ryfylke-react/rtk-query-loader";
Expand Down Expand Up @@ -41,13 +45,55 @@ export const UserRoute = withLoader((props: Props, loader) => {
</article>
);
}, userRouteLoader);
```

<ul>
<li>
<a
href="https://codesandbox.io/s/ryfylke-react-rtk-query-loader-withloader-example-pzgqkf?file=/src/App.tsx"
target="_blank"
>
Live example on CodeSandbox
</a>
</li>
</ul>

</Tab>

<Tab label="<AwaitLoader>" value="load">

If you prefer not using higher order components, or want to use the loader in a more conditional way, you can use the &lt;`AwaitLoader` /&gt; component.

```tsx {6-18}
import { AwaitLoader } from "@ryfylke-react/rtk-query-loader";
import { userRouteLoader } from "./baseLoader";

const UserRoute = () => {
return (
<AwaitLoader
loader={userRouteLoader}
render={({ queries }) => (
<article>
<header>
<h2>{queries.user.data.name}</h2>
</header>
<main>
{queries.posts.data.map((post) => (...))}
</main>
</article>
)}
/>
);
};
```

</Tab>
<Tab label="Using useLoader" value="useLoader" default>
<Tab label="useLoader" value="useLoader">

Every `Loader` contains an actual hook that you can call to run all the queries and aggregate their statuses as if they were just one joined query.

This is convenient if you want to simply use the data, but not the loading and error states of the loader.

```tsx {3,9}
import { userRouteLoader } from "./baseLoader";

Expand All @@ -70,5 +116,9 @@ const UserRoute = (props: Props) => {
};
```

<ul>
<li>
<a href="https://codesandbox.io/s/ryfylke-react-rtk-query-loader-useloader-example-qysfvt?file=/src/App.tsx" target="_blank">
Live example on Codesandbox</a></li></ul>
</Tab>
</Tabs>
45 changes: 45 additions & 0 deletions docs/docs/Reference/awaitloader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
sidebar_position: 7
---

# AwaitLoader

AwaitLoader is a component that consumes a loader and then renders the proper load/fetch/error states, and `props.render` on success.

## Arguments

- **`loader`** - The loader that contains the queries you want to await.
- **`render`** - A function that recieves the loader data and should return a ReactElement.
- **`args`** - Takes in the expected props for the given loader.

```tsx
const loader = createLoader({
queriesArg: (props: { name: string }) => props.name,
useQueries: (name) => ({
queries: {
pokemon: useGetPokemonQuery(name),
},
}),
});

const App = () => {
return (
<div>
<AwaitLoader
loader={loader}
args={{ name: "charizard" }}
render={(data) => (
<pre>{JSON.stringify(data.queries.pokemon.data)}</pre>
)}
/>
<AwaitLoader
loader={loader}
args={{ name: "pikachu" }}
render={(data) => (
<pre>{JSON.stringify(data.queries.pokemon.data)}</pre>
)}
/>
</div>
);
};
```
2 changes: 1 addition & 1 deletion docs/docs/Reference/consumer-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ sidebar_position: 5

Helper for typing your expected consumer props for the loader.

```typescript
```typescript {1-3,6}
type PokemonLoaderProps = ConsumerProps<{
name: string;
}>;
Expand Down
6 changes: 1 addition & 5 deletions docs/docs/Reference/use-create-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,5 @@ const loader = createLoader({
```

:::caution
You lose some great features from RTK query when using `useCreateQuery`.

When possible, try to stick to using actual queries, created from a `@reduxjs/toolkit` API.
You can look at this feature like an escape-hatch that allows you to pass other
data through the loader as well.
You lose some great features from RTK query when using `useCreateQuery`, like global query invalidation (beyond the dependency array), request cancellation and caching.
:::
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ryfylke-react/rtk-query-loader",
"version": "1.0.32",
"version": "1.0.4",
"description": "Lets you create reusable, extendable RTK loaders for React components.",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
Expand Down
46 changes: 46 additions & 0 deletions src/AwaitLoader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from "react";
import * as Types from "./types";
import { withLoader } from "./withLoader";
type ReactType = typeof React;
let R = React;

type AwaitLoaderProps<
TProps extends Record<string, any>,
TReturn,
TArg = never
> = [TArg] extends [never]
? {
loader: Types.Loader<TProps, TReturn, any, any, any, TArg>;
render: (data: TReturn) => React.ReactElement;
}
: {
loader: Types.Loader<TProps, TReturn, any, any, any, TArg>;
render: (data: TReturn) => React.ReactElement;
args: TProps;
};

export const AwaitLoader = <
TProps extends Record<string, any>,
TReturn extends unknown,
TArg = never
>(
args: AwaitLoaderProps<TProps, TReturn, TArg>
) => {
const Component = R.useCallback(
withLoader(
(_, loaderData) => args.render(loaderData),
args.loader
),
[]
);
return R.createElement(
Component,
"args" in args ? args.args : ({} as TProps),
null
);
};

export const _testLoad = (react: any) => {
R = react as ReactType;
return AwaitLoader;
};
39 changes: 30 additions & 9 deletions src/createQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import * as Types from "./types";
type ReactType = typeof React;
let R = React;

let requestIdCount = 0;
const requestIdGenerator = () => {
requestIdCount += 1;
return `usecreatequery-${requestIdCount}`;
};

/**
* Creates a query from an async getter function.
*
Expand All @@ -18,6 +24,8 @@ export const useCreateQuery = <T extends unknown>(
getter: Types.CreateQueryGetter<T>,
dependencies?: any[]
): Types.UseQueryResult<T> => {
const safeDependencies = dependencies ?? [];
const requestId = R.useRef(requestIdGenerator()).current;
const [state, dispatch] = R.useReducer(
(
state: Types.UseQueryResult<T>,
Expand All @@ -32,6 +40,8 @@ export const useCreateQuery = <T extends unknown>(
isFetching: false,
isLoading: true,
isUninitialized: false,
startedTimeStamp: Date.now(),
refetch: action.payload.refetch,
};
case "fetch":
return {
Expand All @@ -41,6 +51,8 @@ export const useCreateQuery = <T extends unknown>(
isError: false,
isFetching: true,
isUninitialized: false,
startedTimeStamp: Date.now(),
refetch: action.payload.refetch,
};
case "success":
return {
Expand All @@ -51,6 +63,8 @@ export const useCreateQuery = <T extends unknown>(
isUninitialized: false,
isSuccess: true,
data: action.payload.data,
currentData: action.payload.data,
fulfilledTimeStamp: Date.now(),
};
case "error":
return {
Expand All @@ -61,6 +75,7 @@ export const useCreateQuery = <T extends unknown>(
isUninitialized: false,
isError: true,
error: action.payload.error,
fulfilledTimeStamp: Date.now(),
};
default:
return state;
Expand All @@ -78,18 +93,13 @@ export const useCreateQuery = <T extends unknown>(
error: undefined,
endpointName: "",
fulfilledTimeStamp: 0,
originalArgs: undefined,
requestId: "",
originalArgs: safeDependencies,
requestId,
startedTimeStamp: 0,
}
);

R.useEffect(() => {
if (state.data === undefined) {
dispatch({ type: "load" });
} else {
dispatch({ type: "fetch" });
}
const runQuery = (overrideInitialized?: boolean) => {
const fetchData = async () => {
try {
const data = await getter();
Expand All @@ -99,8 +109,19 @@ export const useCreateQuery = <T extends unknown>(
}
};

dispatch({
type: overrideInitialized
? "fetch"
: state.isUninitialized
? "load"
: "fetch",
payload: { refetch: () => runQuery(true) },
});

fetchData();
}, [...(dependencies ?? [])]);
};

R.useEffect(() => runQuery(), safeDependencies);

return state;
};
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export type {
WithLoaderArgs,
} from "./types";
export { withLoader } from "./withLoader";
export { AwaitLoader } from "./AwaitLoader";
18 changes: 18 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { SerializedError } from "@reduxjs/toolkit";
import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query";
import { ReactElement } from "react";

export type Unwrap<T> = {
[K in keyof T]: T[K];
} & {};

export type AllNever<
TQueries,
TDeferred,
Expand Down Expand Up @@ -86,6 +90,14 @@ export type DataShapeInput<
queries?: TQueries;
deferredQueries?: TDeferred;
payload?: TPayload;
/** This should be specified at the **top level** of `createLoader` or `extend` */
onLoading?: never;
/** This should be specified at the **top level** of `createLoader` or `extend` */
onError?: never;
/** This should be specified at the **top level** of `createLoader` or `extend` */
onFetching?: never;
/** This should be specified at the **top level** of `createLoader` or `extend` */
whileFetching?: never;
};

export type ResolveDataShape<
Expand Down Expand Up @@ -387,9 +399,15 @@ export type CreateQueryGetter<T extends unknown> =
export type CreateQueryReducerAction<T extends unknown> =
| {
type: "load";
payload: {
refetch: () => void;
};
}
| {
type: "fetch";
payload: {
refetch: () => void;
};
}
| {
type: "error";
Expand Down
Loading

0 comments on commit 47f95dd

Please sign in to comment.