Skip to content

Commit

Permalink
refactor: move hook reducer into hook factory file
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore committed Jun 18, 2021
1 parent c666519 commit b06e831
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 187 deletions.
124 changes: 124 additions & 0 deletions src/createClientHook.ts
@@ -0,0 +1,124 @@
import * as React from "react";
import * as prismic from "@prismicio/client";

import { usePrismicClient } from "./usePrismicClient";

type PrismicClientError =
| prismic.PrismicError
| prismic.ParsingError
| prismic.ForbiddenError;

const enum StateMachineStateType {
IDLE,
PENDING,
SUCCEEDED,
FAILED,
}

export type StateMachineState<TData> = {
state: StateMachineStateType;
data?: TData;
error?: PrismicClientError;
};

type StateMachineAction<TData> =
| [type: "start"]
| [type: "succeed", payload: TData]
| [type: "fail", payload: PrismicClientError];

const reducer = <TData>(
state: StateMachineState<TData>,
action: StateMachineAction<TData>,
): StateMachineState<TData> => {
if (action[0] === "start") {
return { state: StateMachineStateType.PENDING };
} else if (action[0] === "succeed") {
return { state: StateMachineStateType.SUCCEEDED, data: action[1] };
} else if (action[0] === "fail") {
return { ...state, state: StateMachineStateType.FAILED, error: action[1] };
}

return state;
};

const initialState = { state: StateMachineStateType.IDLE } as const;

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type ClientPrototype = typeof prismic.Client.prototype;

export type ClientMethodParameters<MethodName extends keyof ClientPrototype> =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ClientPrototype[MethodName] extends (...args: any[]) => any
? Parameters<ClientPrototype[MethodName]>
: never;

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

const getParamHookDependencies = (
params: ClientMethodParameters<"get">[0] = {},
) => {
return [
params.ref,
params.lang,
params.page,
params.after,
params.fetch,
params.pageSize,
params.orderings,
params.fetchLinks,
params.graphQuery,
params.predicates,
params.accessToken,
];
};

export const createClientHook = <
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TMethod extends (...args: any[]) => Promise<any>,
TArgs extends Parameters<TMethod>,
>(
method: TMethod,
) => {
return (
...args: TArgs
): [
data: StateMachineState<UnwrapPromise<ReturnType<TMethod>>>["data"],
state: Pick<
StateMachineState<UnwrapPromise<ReturnType<TMethod>>>,
"state" | "error"
>,
] => {
const params:
| (ClientMethodParameters<"get">[0] & HookOnlyParameters)
| undefined = args[args.length - 1];
const client = usePrismicClient(params?.client);

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

React.useEffect(
() => {
dispatch(["start"]);
method
.apply(client, args)
.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(-1), ...getParamHookDependencies(params)],
);

return React.useMemo(
() => [state.data, { state: state.state, error: state.error }],
[state],
);
};
};
78 changes: 4 additions & 74 deletions src/hooks.ts
@@ -1,80 +1,10 @@
import * as React from "react";
import * as prismic from "@prismicio/client";

import { usePrismicClient } from "./usePrismicClient";
import {
StateMachineState,
usePrismicClientStateMachine,
} from "./usePrismicClientStateMachine";

type ClientPrototype = typeof prismic.Client.prototype;

type ClientMethodParameters<MethodName extends keyof ClientPrototype> =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ClientPrototype[MethodName] extends (...args: any[]) => any
? Parameters<ClientPrototype[MethodName]>
: never;

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

const getParamHookDependencies = (
params: ClientMethodParameters<"get">[0] = {},
) => {
return [
params.ref,
params.lang,
params.page,
params.after,
params.fetch,
params.pageSize,
params.orderings,
params.fetchLinks,
params.graphQuery,
params.predicates,
params.accessToken,
];
};

const createClientHook = <
TMethod extends (...args: any[]) => Promise<any>,
TArgs extends Parameters<TMethod>,
>(
method: TMethod,
) => {
return (
...args: TArgs
): [
data: StateMachineState<ReturnType<TMethod>>["data"],
state: Pick<StateMachineState<ReturnType<TMethod>>, "state" | "error">,
] => {
const params:
| (ClientMethodParameters<"get">[0] & HookOnlyParameters)
| undefined = args[args.length - 1];
const client = usePrismicClient(params?.client);
const [state, actions] =
usePrismicClientStateMachine<ReturnType<TMethod>>();

React.useEffect(
() => {
actions.start();
method
.apply(client, args)
.then((result) => actions.succeed(result))
.catch((error) => actions.fail(error));
},
// We must disable exhaustive-deps to optimize providing `params` deps.
// eslint-disable-next-line react-hooks/exhaustive-deps
[actions, client, ...args.slice(-1), ...getParamHookDependencies(params)],
);

return React.useMemo(
() => [state.data, { state: state.state, error: state.error }],
[state],
);
};
};
ClientMethodParameters,
HookOnlyParameters,
createClientHook,
} from "./createClientHook";

const proto = prismic.Client.prototype;

Expand Down
71 changes: 0 additions & 71 deletions src/usePrismicClientStateMachine.ts

This file was deleted.

42 changes: 0 additions & 42 deletions src/usePrismicDocumentByUID.ts

This file was deleted.

0 comments on commit b06e831

Please sign in to comment.