Skip to content

Commit

Permalink
feat(prefetch): add usePrefetchQuery hook (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
amen-souissi authored May 17, 2021
1 parent 55c8049 commit a1b3b2c
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 15 deletions.
34 changes: 28 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Table of contents
* [Usage](#usage)
* [Query](#query-component)
* [useQuery](#usequery)
* [usePrefetchQuery](#usePrefetchQuery)
* [useInfiniteQuery](#useinfinitequery)
* [Mutation / useMutation](#mutationusemutation)
* [Other useful Hooks](#other-useful-hooks)
Expand Down Expand Up @@ -88,7 +89,7 @@ import { Query } from "@decathlon/moon";

const MyComponent = () => {
return (
<Query<QueryVariables, QueryResponse, QueryError>
<Query<QueryVariables, QueryResponse, QueryData, QueryError>
id="queryId"
source="FOO"
endPoint="/users"
Expand All @@ -114,7 +115,7 @@ The same query with the **useQuery** hook
import { useQuery } from "@decathlon/moon";

const MyComponent = () => {
const [{ isLoading, error }, { refetch }] = useQuery<QueryVariables, QueryData, QueryError>({
const [{ isLoading, error }, { refetch }] = useQuery<QueryVariables, QueryResponse, QueryData, QueryError>({
id: "queryId",
source: "FOO",
endPoint: "/users",
Expand All @@ -130,6 +131,27 @@ const MyComponent = () => {
```
Internally useQuery use the **react-query**'s useQuery hook connected to your HTTP client with a configuration allowing better cache management (fetch policy) and better referencing (management of query identifiers adapted to the use of HTTP clients, **useQueryState/useQueryResult**...) of requests for REST clients.

usePrefetchQuery
--------
If you're lucky enough, you may know enough about what your users will do to be able to prefetch the data they need before it's needed! If this is the case, you can use the usePrefetchQuery hook. This hook return a prefetch function to prefetch the results of a query to be placed into the cache:

```js
import { usePrefetchQuery } from "@decathlon/moon";

const MyComponent = ({page}) => {
const prefetchQuery = usePrefetchQuery<QueryVariables, QueryResponse, QueryData>({
id: "queryId",
source: "FOO",
endPoint: "/users",
variables: { foo: "bar", page: page + 1 },
// options: {...} // the http client config
// queryConfig: {...} // the react-query config
});
...
};
```
Internally usePrefetchQuery use the **react-query**'s queryClient.prefetchQuery method connected to your HTTP client.

useInfiniteQuery
---------------

Expand All @@ -146,7 +168,7 @@ interface PageVariables {
}

const MyComponent = () => {
const [{ isLoading, error, data }] = useInfiniteQuery<QueryVariables, PageVariables, QueryData, QueryError>({
const [{ isLoading, error, data }] = useInfiniteQuery<QueryVariables, PageVariables, QueryResponse, QueryData, QueryError>({
source: "FOO",
endPoint: "/comments",
variables: { user: "bar" },
Expand Down Expand Up @@ -195,7 +217,7 @@ const MyComponent = () => {
The same mutation with **useMutation**:

```js
import { useQuery } from '@decathlon/moon';
import { useMutation } from '@decathlon/moon';

const MyComponent = () => {
const [{ error, data }, { mutate }] = useMutation<MutationResponse, MutationVariables>({
Expand Down Expand Up @@ -393,7 +415,7 @@ Query props
This the Typescript interface of the Query/useQuery component/hook.

```js
export interface IQueryProps<QueryVariables = any, QueryResponse = any, QueryError = any, QueryConfig = any> {
export interface IQueryProps<QueryVariables = any, QueryResponse = any, QueryData=QueryResponse, QueryError = any, QueryConfig = any> {
id?: string;
/** The Link id of the http client. */
source: string;
Expand Down Expand Up @@ -427,7 +449,7 @@ InfiniteQuery props
===================

```js
export interface IInfiniteQueryProps<QueryVariables = any, QueryResponse = any, QueryError = any, QueryConfig = any> {
export interface IInfiniteQueryProps<QueryVariables = any, QueryResponse = any, QueryData = QueryResponse, QueryError = any, QueryConfig = any> {
id?: string;
/** The Link id of the http client. */
source?: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/moon-graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const requestInterceptors = [{ onFulfilled: setLanguage }];
const responseInterceptors = [{ onFulfilled: successHandler }];


const links: ILink<graphqlInstance, graphqlRequestConfig, graphqlResponse>[] = [
const links: ILink<GraphqlInstance, GraphqlRequestConfig, GraphqlResponse>[] = [
{
id: "FOO",
config: { baseURL: "http://foo.com" }, // RequestInit
Expand Down
2 changes: 1 addition & 1 deletion packages/moon-graphql/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@decathlon/moon-graphql",
"version": "1.0.0-alpha.1",
"version": "1.0.0-alpha.2",
"description": "GraphQL for moon",
"author": "Decathlon",
"license": "Apache-2.0",
Expand Down
34 changes: 28 additions & 6 deletions packages/moon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Table of contents
* [Usage](#usage)
* [Query](#query-component)
* [useQuery](#usequery)
* [usePrefetchQuery](#usePrefetchQuery)
* [useInfiniteQuery](#useinfinitequery)
* [Mutation / useMutation](#mutationusemutation)
* [Other useful Hooks](#other-useful-hooks)
Expand Down Expand Up @@ -88,7 +89,7 @@ import { Query } from "@decathlon/moon";

const MyComponent = () => {
return (
<Query<QueryVariables, QueryResponse, QueryError>
<Query<QueryVariables, QueryResponse, QueryData, QueryError>
id="queryId"
source="FOO"
endPoint="/users"
Expand All @@ -114,7 +115,7 @@ The same query with the **useQuery** hook
import { useQuery } from "@decathlon/moon";

const MyComponent = () => {
const [{ isLoading, error }, { refetch }] = useQuery<QueryVariables, QueryData, QueryError>({
const [{ isLoading, error }, { refetch }] = useQuery<QueryVariables, QueryResponse, QueryData, QueryError>({
id: "queryId",
source: "FOO",
endPoint: "/users",
Expand All @@ -130,6 +131,27 @@ const MyComponent = () => {
```
Internally useQuery use the **react-query**'s useQuery hook connected to your HTTP client with a configuration allowing better cache management (fetch policy) and better referencing (management of query identifiers adapted to the use of HTTP clients, **useQueryState/useQueryResult**...) of requests for REST clients.

usePrefetchQuery
--------
If you're lucky enough, you may know enough about what your users will do to be able to prefetch the data they need before it's needed! If this is the case, you can use the usePrefetchQuery hook. This hook return a prefetch function to prefetch the results of a query to be placed into the cache:

```js
import { usePrefetchQuery } from "@decathlon/moon";

const MyComponent = ({page}) => {
const prefetchQuery = usePrefetchQuery<QueryVariables, QueryResponse, QueryData>({
id: "queryId",
source: "FOO",
endPoint: "/users",
variables: { foo: "bar", page: page + 1 },
// options: {...} // the http client config
// queryConfig: {...} // the react-query config
});
...
};
```
Internally usePrefetchQuery use the **react-query**'s queryClient.prefetchQuery method connected to your HTTP client.

useInfiniteQuery
---------------

Expand All @@ -146,7 +168,7 @@ interface PageVariables {
}

const MyComponent = () => {
const [{ isLoading, error, data }] = useInfiniteQuery<QueryVariables, PageVariables, QueryData, QueryError>({
const [{ isLoading, error, data }] = useInfiniteQuery<QueryVariables, PageVariables, QueryResponse, QueryData, QueryError>({
source: "FOO",
endPoint: "/comments",
variables: { user: "bar" },
Expand Down Expand Up @@ -195,7 +217,7 @@ const MyComponent = () => {
The same mutation with **useMutation**:

```js
import { useQuery } from '@decathlon/moon';
import { useMutation } from '@decathlon/moon';

const MyComponent = () => {
const [{ error, data }, { mutate }] = useMutation<MutationResponse, MutationVariables>({
Expand Down Expand Up @@ -393,7 +415,7 @@ Query props
This the Typescript interface of the Query/useQuery component/hook.

```js
export interface IQueryProps<QueryVariables = any, QueryResponse = any, QueryError = any, QueryConfig = any> {
export interface IQueryProps<QueryVariables = any, QueryResponse = any, QueryData=QueryResponse, QueryError = any, QueryConfig = any> {
id?: string;
/** The Link id of the http client. */
source: string;
Expand Down Expand Up @@ -427,7 +449,7 @@ InfiniteQuery props
===================

```js
export interface IInfiniteQueryProps<QueryVariables = any, QueryResponse = any, QueryError = any, QueryConfig = any> {
export interface IInfiniteQueryProps<QueryVariables = any, QueryResponse = any, QueryData = QueryResponse, QueryError = any, QueryConfig = any> {
id?: string;
/** The Link id of the http client. */
source?: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/moon/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@decathlon/moon",
"version": "4.1.2",
"version": "4.2.0",
"description": "A featured, production ready caching REST client for every React UI",
"author": "Decathlon",
"license": "Apache-2.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/moon/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export * from "./query";
export { default as useQuery } from "./useQuery";
export * from "./useQuery";

export { default as usePrefetchQuery } from "./usePrefetchQuery";
export * from "./usePrefetchQuery";

export { default as Mutation } from "./mutation";
export * from "./mutation";

Expand Down
37 changes: 37 additions & 0 deletions packages/moon/src/usePrefetchQuery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from "react";
import { UseQueryOptions as ReactQueryConfig } from "react-query";

import { useMoon } from "./hooks";
import { getQueryId, IQueryProps } from "./useQuery";
import { ClientConfig } from "./utils";

export default function usePrefetchQuery<
QueryVariables = any,
QueryResponse = any,
QueryData = QueryResponse,
QueryError = any,
QueryConfig extends ClientConfig = any
>({
id,
source,
endPoint,
variables,
options,
queryConfig
}: IQueryProps<QueryVariables, QueryResponse, QueryData, QueryError, QueryConfig>): () => Promise<void> {
const { client, store } = useMoon();
const queryId = getQueryId({ id, source, endPoint, variables });

const queryOptions: ReactQueryConfig<QueryResponse, QueryError, QueryData> = React.useMemo(
() => store.defaultQueryObserverOptions(queryConfig),
[queryConfig, store]
);

function fetch() {
return client.query<QueryVariables, QueryResponse, QueryConfig>(source, endPoint, variables, options);
}
async function prefetch() {
await store.prefetchQuery(queryId, fetch, queryOptions);
}
return prefetch;
}
37 changes: 37 additions & 0 deletions packages/moon/test/integration/hooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { QueryState } from "react-query/types/core/query";

import { usePrevValue, useQueryResult, useQueriesResults, useQueryState, useQueriesStates } from "../../src/hooks";
import useQuery from "../../src/useQuery";
import usePrefetchQuery from "../../src/usePrefetchQuery";
import MoonProvider from "../../src/moonProvider";
import { links } from "../moon-client.test";
import { withQueryResult, withQueriesResults } from "../../src/query";
Expand Down Expand Up @@ -222,4 +223,40 @@ describe("Hooks", () => {
expect(value).toEqual(newPrevProps);
expect(newValue).not.toEqual(value);
});

test("should render the prefetch query", async () => {
const get = jest.fn().mockImplementation(() => Promise.resolve(response));
const clientFactory = getMockedClientFactory({ get });

const wrapper = ({ children }: { children?: any }) => (
<MoonProvider links={links} clientFactory={clientFactory}>
{children}
</MoonProvider>
);
const variables = { foo: "bar" };
const { result } = renderHook(
() =>
usePrefetchQuery<QueryVariables, typeof response, typeof response, any, MockedClientConfig>({
id: "myQuery4",
source: "FOO",
endPoint: "/users",
variables
}),
{ wrapper }
);
expect(get).toBeCalledTimes(0);
// call prefetch
await result.current();
expect(get).toBeCalledTimes(1);
expect(get).toBeCalledWith("/users", { params: { foo: "bar" } });
const { result: cachedResult } = renderHook(
//@ts-ignore data can't be undefined
() => useQueryState<QueryData, number>("myQuery4", response => response?.data.users[0].id),
{
wrapper
}
);

expect(cachedResult.current).toEqual(response.users[0].id);
});
});

0 comments on commit a1b3b2c

Please sign in to comment.