Skip to content

Commit

Permalink
feat: support document type on client hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore committed Aug 3, 2021
1 parent 9bac238 commit 0a0427b
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 106 deletions.
158 changes: 93 additions & 65 deletions src/hooks.ts
@@ -1,113 +1,141 @@
import * as prismic from "@prismicio/client";
import * as prismicT from "@prismicio/types";

import {
ClientHookReturnType,
ClientMethodParameters,
HookOnlyParameters,
createClientHook,
} from "./lib/createClientHook";
useStatefulPrismicClientMethod,
} from "./useStatefulPrismicClientMethod";

const proto = prismic.Client.prototype;

export const usePrismicDocuments = createClientHook<
typeof proto.get,
[params?: ClientMethodParameters<"get">[0] & HookOnlyParameters]
>(proto.get);

export const useFirstPrismicDocument = createClientHook<
typeof proto.getFirst,
[params?: ClientMethodParameters<"getFirst">[0] & HookOnlyParameters]
>(proto.getFirst);

export const useAllPrismicDocuments = createClientHook<
typeof proto.getAll,
[params?: ClientMethodParameters<"getAll">[0] & HookOnlyParameters]
>(proto.getAll);

export const usePrismicDocumentByID = createClientHook<
typeof proto.getByID,
[
export const usePrismicDocuments = <TDocument extends prismicT.PrismicDocument>(
...args: [params?: ClientMethodParameters<"get">[0] & HookOnlyParameters]
): ClientHookReturnType<prismic.Query<TDocument>> =>
useStatefulPrismicClientMethod(proto.get, args);

export const useFirstPrismicDocument = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [params?: ClientMethodParameters<"getFirst">[0] & HookOnlyParameters]
): ClientHookReturnType<TDocument> =>
useStatefulPrismicClientMethod(proto.getFirst, args);

export const useAllPrismicDocuments = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [params?: ClientMethodParameters<"getAll">[0] & HookOnlyParameters]
): ClientHookReturnType<TDocument[]> =>
useStatefulPrismicClientMethod(proto.getAll, args);

export const usePrismicDocumentByID = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
id: ClientMethodParameters<"getByID">[0],
params?: ClientMethodParameters<"getByID">[1] & HookOnlyParameters,
]
>(proto.getByID);

export const usePrismicDocumentsByIDs = createClientHook<
typeof proto.getByIDs,
[
ids: ClientMethodParameters<"getByIDs">[0],
): ClientHookReturnType<TDocument> =>
useStatefulPrismicClientMethod(proto.getByID, args);

export const usePrismicDocumentsByIDs = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
id: ClientMethodParameters<"getByIDs">[0],
params?: ClientMethodParameters<"getByIDs">[1] & HookOnlyParameters,
]
>(proto.getByIDs);

export const useAllPrismicDocumentsByIDs = createClientHook<
typeof proto.getAllByIDs,
[
ids: ClientMethodParameters<"getAllByIDs">[0],
): ClientHookReturnType<prismic.Query<TDocument>> =>
useStatefulPrismicClientMethod(proto.getByIDs, args);

export const useAllPrismicDocumentsByIDs = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
id: ClientMethodParameters<"getAllByIDs">[0],
params?: ClientMethodParameters<"getAllByIDs">[1] & HookOnlyParameters,
]
>(proto.getAllByIDs);
): ClientHookReturnType<TDocument[]> =>
useStatefulPrismicClientMethod(proto.getAllByIDs, args);

export const usePrismicDocumentByUID = createClientHook<
typeof proto.getByUID,
[
export const usePrismicDocumentByUID = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
documentType: ClientMethodParameters<"getByUID">[0],
uid: ClientMethodParameters<"getByUID">[1],
params?: ClientMethodParameters<"getByUID">[2] & HookOnlyParameters,
]
>(proto.getByUID);
): ClientHookReturnType<TDocument> =>
useStatefulPrismicClientMethod(proto.getByUID, args);

export const useSinglePrismicDocument = createClientHook<
typeof proto.getSingle,
[
export const useSinglePrismicDocument = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
documentType: ClientMethodParameters<"getSingle">[0],
params?: ClientMethodParameters<"getSingle">[1] & HookOnlyParameters,
]
>(proto.getSingle);
): ClientHookReturnType<TDocument> =>
useStatefulPrismicClientMethod(proto.getSingle, args);

export const usePrismicDocumentsByType = createClientHook<
typeof proto.getByType,
[
export const usePrismicDocumentsByType = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
documentType: ClientMethodParameters<"getByType">[0],
params?: ClientMethodParameters<"getByType">[1] & HookOnlyParameters,
]
>(proto.getByType);
): ClientHookReturnType<prismic.Query<TDocument>> =>
useStatefulPrismicClientMethod(proto.getByType, args);

export const useAllPrismicDocumentsByType = createClientHook<
typeof proto.getAllByType,
[
export const useAllPrismicDocumentsByType = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
documentType: ClientMethodParameters<"getAllByType">[0],
params?: ClientMethodParameters<"getAllByType">[1] & HookOnlyParameters,
]
>(proto.getAllByType);
): ClientHookReturnType<TDocument[]> =>
useStatefulPrismicClientMethod(proto.getAllByType, args);

export const usePrismicDocumentsByTag = createClientHook<
typeof proto.getByTag,
[
export const usePrismicDocumentsByTag = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
tag: ClientMethodParameters<"getByTag">[0],
params?: ClientMethodParameters<"getByTag">[1] & HookOnlyParameters,
]
>(proto.getByTag);
): ClientHookReturnType<prismic.Query<TDocument>> =>
useStatefulPrismicClientMethod(proto.getByTag, args);

export const useAllPrismicDocumentsByTag = createClientHook<
typeof proto.getAllByTag,
[
export const useAllPrismicDocumentsByTag = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
tag: ClientMethodParameters<"getAllByTag">[0],
params?: ClientMethodParameters<"getAllByTag">[1] & HookOnlyParameters,
]
>(proto.getAllByTag);
): ClientHookReturnType<TDocument[]> =>
useStatefulPrismicClientMethod(proto.getAllByTag, args);

export const usePrismicDocumentsByTags = createClientHook<
typeof proto.getByTags,
[
export const usePrismicDocumentsByTags = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
tag: ClientMethodParameters<"getByTags">[0],
params?: ClientMethodParameters<"getByTags">[1] & HookOnlyParameters,
]
>(proto.getByTags);
): ClientHookReturnType<prismic.Query<TDocument>> =>
useStatefulPrismicClientMethod(proto.getByTags, args);

export const useAllPrismicDocumentsByTags = createClientHook<
typeof proto.getAllByTags,
[
export const useAllPrismicDocumentsByTags = <
TDocument extends prismicT.PrismicDocument,
>(
...args: [
tag: ClientMethodParameters<"getAllByTags">[0],
params?: ClientMethodParameters<"getAllByTags">[1] & HookOnlyParameters,
]
>(proto.getAllByTags);
): ClientHookReturnType<TDocument[]> =>
useStatefulPrismicClientMethod(proto.getAllByTags, args);
@@ -1,8 +1,8 @@
import * as React from "react";
import * as prismic from "@prismicio/client";

import { PrismicHookState } from "../types";
import { usePrismicClient } from "../usePrismicClient";
import { PrismicHookState } from "./types";
import { usePrismicClient } from "./usePrismicClient";

type PrismicClientError =
| prismic.PrismicError
Expand Down Expand Up @@ -49,6 +49,7 @@ export type ClientMethodParameters<MethodName extends keyof ClientPrototype> =

export type HookOnlyParameters = {
client?: prismic.Client;
skip?: boolean;
};

const getParamHookDependencies = (
Expand All @@ -70,7 +71,7 @@ const getParamHookDependencies = (
};

/**
* Determiens if a value is a `@prismicio/client` params object.
* Determines if a value is a `@prismicio/client` params object.
*
* @param value The value to check.
*
Expand All @@ -83,6 +84,19 @@ const isParams = (
return typeof value === "object" && value !== null && !Array.isArray(value);
};

/**
* The return value of a `@prismicio/client` React hook.
*
* @typeParam TData Data returned by the client.
*/
export type ClientHookReturnType<TData = unknown> = [
/** Data returned by the client. */
data: TData | undefined,

/** The current state of the hook's client method call. */
state: Pick<StateMachineState<TData>, "state" | "error">,
];

/**
* Creates a React hook that forwards arguments to a specific method of a `@prismicio/client` instance. The created hook has its own internal state manager to report async status, such as pending or error statuses.
*
Expand All @@ -92,53 +106,49 @@ const isParams = (
*
* @internal
*/
export const createClientHook = <
export const useStatefulPrismicClientMethod = <
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TMethod extends (...args: any[]) => Promise<any>,
TArgs extends Parameters<TMethod>,
TData extends UnwrapPromise<ReturnType<TMethod>>,
>(
method: TMethod,
) => {
return (
...args: TArgs
): [
data: StateMachineState<UnwrapPromise<ReturnType<TMethod>>>["data"],
state: Pick<
args: TArgs,
): ClientHookReturnType<TData> => {
const lastArg = args[args.length - 1];
const {
client: explicitClient,
skip,
...params
} = isParams(lastArg) ? lastArg : ({} as HookOnlyParameters);
const argsWithoutParams = isParams(lastArg) ? args.slice(0, -1) : args;

const client = usePrismicClient(explicitClient);

const [state, dispatch] = React.useReducer<
React.Reducer<
StateMachineState<UnwrapPromise<ReturnType<TMethod>>>,
"state" | "error"
>,
] => {
const lastArg = args[args.length - 1];
const { client: explicitClient, ...params } = isParams(lastArg)
? lastArg
: ({} as HookOnlyParameters);
const argsWithoutParams = isParams(lastArg) ? args.slice(0, -1) : args;

const client = usePrismicClient(explicitClient);

const [state, dispatch] = React.useReducer<
React.Reducer<
StateMachineState<UnwrapPromise<ReturnType<TMethod>>>,
StateMachineAction<UnwrapPromise<ReturnType<TMethod>>>
>
>(reducer, initialState);

React.useEffect(
() => {
StateMachineAction<UnwrapPromise<ReturnType<TMethod>>>
>
>(reducer, initialState);

React.useEffect(
() => {
if (state.state === PrismicHookState.IDLE && !skip) {
dispatch(["start"]);
method
.call(client, ...argsWithoutParams, params)
.then((result) => dispatch(["succeed", result]))
.catch((error) => dispatch(["fail", error]));
},
// We must disable exhaustive-deps to optimize providing `params` deps.
// eslint-disable-next-line react-hooks/exhaustive-deps
[client, ...args.slice(0, -1), ...getParamHookDependencies(params)],
);

return React.useMemo(
() => [state.data, { state: state.state, error: state.error }],
[state],
);
};
}
},
// We must disable exhaustive-deps to optimize providing `params` deps.
// eslint-disable-next-line react-hooks/exhaustive-deps
[client, ...args.slice(0, -1), ...getParamHookDependencies(params)],
);

return React.useMemo(
() => [state.data, { state: state.state, error: state.error }],
[state],
);
};

0 comments on commit 0a0427b

Please sign in to comment.