From 86e2643dbaceddfdc4d94599e6ae1dc4581e912d Mon Sep 17 00:00:00 2001 From: Sanna Jammeh <50969683+sannajammeh@users.noreply.github.com> Date: Sat, 24 Jun 2023 20:12:37 +0200 Subject: [PATCH] fix(client:mutation): fix SWR Mutation argument update on latest version Mutation generic argument changed location of Key and ExtraArg causing the input of trigger fn to be the path i.e "recipes.complete" instead of the data. BREAKING CHANGE: Breaking change, this patch requires the lastest SWR version `2.2.0` in order to work. Peer dependencies are updated fix: #46 --- apps/demo/src/pages/simple-mutation.tsx | 221 +++++------ packages/client/src/createSWRProxyHooks.tsx | 206 +++++----- packages/client/src/shared/createSWRHooks.tsx | 364 +++++++++--------- 3 files changed, 399 insertions(+), 392 deletions(-) diff --git a/apps/demo/src/pages/simple-mutation.tsx b/apps/demo/src/pages/simple-mutation.tsx index a2b2c79..bee8fd8 100644 --- a/apps/demo/src/pages/simple-mutation.tsx +++ b/apps/demo/src/pages/simple-mutation.tsx @@ -10,119 +10,120 @@ import { useState } from "react"; import useSWRImmutable from "swr/immutable"; const MutationCard = ({ - throwError, - setThrowError, -}: { throwError: boolean; setThrowError: (value: boolean) => void }) => { - const { query } = useRouter(); - const { client } = api.useContext(); - const { - data: user, - isMutating, - trigger, - error, - reset, - } = api.users.create.useSWRMutation({ - throwOnError: false, - }); - - const { data: hasReset } = useSWRImmutable("reset", () => { - return client.users.reset.mutate(); - }); - - const handleReset = async () => { - await client.users.reset.mutate(); - reset(); - }; - - if (!hasReset) { - return Waiting for reset...; - } - - if (isMutating) - return ( - -
- Loading... -
-
- ); - - return ( - <> - -

- Create user -

-
- - - -
- - -
-
-
- {user && ( - <> - - {user.name}} - description={`ID: ${user.id}`} - squared - /> - - - )} - - {error && ( - <> - -

Error

-
{error.message}
-
- - )} - - ); + throwError, + setThrowError, +}: { + throwError: boolean; + setThrowError: (value: boolean) => void; +}) => { + const { query } = useRouter(); + const { client } = api.useContext(); + const { + data: user, + isMutating, + trigger, + error, + reset, + } = api.users.create.useSWRMutation(); + + const { data: hasReset } = useSWRImmutable("reset", () => { + return client.users.reset.mutate(); + }); + + const handleReset = async () => { + await client.users.reset.mutate(); + reset(); + }; + + if (!hasReset) { + return Waiting for reset...; + } + + if (isMutating) + return ( + +
+ Loading... +
+
+ ); + + return ( + <> + +

+ Create user +

+
+ + + +
+ + +
+
+
+ {user && ( + <> + + {user.name}} + description={`ID: ${user.id}`} + squared + /> + + + )} + + {error && ( + <> + +

Error

+
{error.message}
+
+ + )} + + ); }; const SimpleMutation = () => { - const [throwError, setThrowError] = useState(false); - - return ( - <> - - -

Code

- {throwError ? handleErrorsCode : codePreview} -
- - ); + const [throwError, setThrowError] = useState(false); + + return ( + <> + + +

Code

+ {throwError ? handleErrorsCode : codePreview} +
+ + ); }; export default SimpleMutation; diff --git a/packages/client/src/createSWRProxyHooks.tsx b/packages/client/src/createSWRProxyHooks.tsx index 7e54d77..2873e81 100644 --- a/packages/client/src/createSWRProxyHooks.tsx +++ b/packages/client/src/createSWRProxyHooks.tsx @@ -1,18 +1,18 @@ import { TRPCClientErrorLike } from "@trpc/client"; import { - AnyMutationProcedure, - AnyProcedure, - AnyQueryProcedure, - AnyRouter, - inferProcedureInput, - inferProcedureOutput, - ProcedureRouterRecord, + AnyMutationProcedure, + AnyProcedure, + AnyQueryProcedure, + AnyRouter, + inferProcedureInput, + inferProcedureOutput, + ProcedureRouterRecord, } from "@trpc/server"; import { createFlatProxy, createRecursiveProxy } from "@trpc/server/shared"; import { SWRConfiguration, SWRResponse } from "swr"; import type { - SWRMutationConfiguration, - SWRMutationResponse, + SWRMutationConfiguration, + SWRMutationResponse, } from "swr/mutation"; import { getQueryKey } from "./shared/utils"; @@ -20,56 +20,62 @@ import type { CreateTRPCSWRHooks, TRPCProvider } from "./shared/createSWRHooks"; import type { CreateClient, GetKey } from "./shared/types"; type DecorateProcedure< - TProcedure extends AnyProcedure, - TPath extends string, + TProcedure extends AnyProcedure, + TPath extends string > = TProcedure extends AnyQueryProcedure - ? { - useSWR: >( - input: inferProcedureInput, - opts?: SWRConfiguration & { - isDisabled?: boolean; - }, - ) => SWRResponse>; - - preload: (input: inferProcedureInput) => Promise; - getKey: GetKey; - } - : TProcedure extends AnyMutationProcedure - ? { - useSWRMutation: < - TData = inferProcedureOutput, - TMutationInput = inferProcedureInput, - TError = TRPCClientErrorLike, - >( - opts?: SWRMutationConfiguration, - ) => SWRMutationResponse; - - getKey: GetKey; - } - : never; + ? { + useSWR: >( + input: inferProcedureInput, + opts?: SWRConfiguration & { + isDisabled?: boolean; + } + ) => SWRResponse>; + + preload: (input: inferProcedureInput) => Promise; + getKey: GetKey; + } + : TProcedure extends AnyMutationProcedure + ? { + useSWRMutation: < + TData = inferProcedureOutput, + TMutationInput = inferProcedureInput, + TError = TRPCClientErrorLike + >( + opts?: SWRMutationConfiguration< + TData, + TError, + TPath, + TMutationInput, + TPath + > + ) => SWRMutationResponse; + + getKey: GetKey; + } + : never; /** * @internal */ export type DecoratedProcedureRecord< - TProcedures extends ProcedureRouterRecord, - TPath extends string = "", + TProcedures extends ProcedureRouterRecord, + TPath extends string = "" > = { - [TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter - ? DecoratedProcedureRecord< - TProcedures[TKey]["_def"]["record"], - `${TPath}${TKey & string}.` - > - : TProcedures[TKey] extends AnyProcedure - ? DecorateProcedure - : never; + [TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter + ? DecoratedProcedureRecord< + TProcedures[TKey]["_def"]["record"], + `${TPath}${TKey & string}.` + > + : TProcedures[TKey] extends AnyProcedure + ? DecorateProcedure + : never; }; export type CreateTRPCSWRProxy = { - createClient: CreateClient; - useContext: CreateTRPCSWRHooks["useContext"]; - Provider: TRPCProvider; - SWRConfig: CreateTRPCSWRHooks["SWRConfig"]; + createClient: CreateClient; + useContext: CreateTRPCSWRHooks["useContext"]; + Provider: TRPCProvider; + SWRConfig: CreateTRPCSWRHooks["SWRConfig"]; } & DecoratedProcedureRecord; /** @@ -77,60 +83,60 @@ export type CreateTRPCSWRProxy = { * @internal */ export function createSWRProxyDecoration( - name: string, - hooks: CreateTRPCSWRHooks, + name: string, + hooks: CreateTRPCSWRHooks ) { - return createRecursiveProxy((opts) => { - const args = opts.args; - - const pathCopy = [name, ...opts.path]; - - // The last arg is for instance `.useMutation` or `.useQuery()` - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - // rome-ignore lint/style/noNonNullAssertion: - const lastArg = pathCopy.pop()!; - - // The `path` ends up being something like `post.byId` - const path = pathCopy.join("."); - - if (lastArg === "useSWRMutation") { - return (hooks as any)[lastArg](path, ...args); - } - - const [input, ...rest] = args; - const queryKey = getQueryKey(path, input); - - /** - * Preload does not care about other opts. - */ - if (lastArg === "preload") { - return hooks.preload(queryKey); - } - - /** - * Make sure we error on improper usage - */ - if (lastArg === "getKey") { - const [unserialized] = rest; - if (typeof unserialized !== "boolean" && unserialized !== undefined) { - throw new Error("Expected second argument to be a boolean"); - } - hooks.getKey(queryKey, unserialized); - } - - return (hooks as any)[lastArg](queryKey, ...rest); - }); + return createRecursiveProxy((opts) => { + const args = opts.args; + + const pathCopy = [name, ...opts.path]; + + // The last arg is for instance `.useMutation` or `.useQuery()` + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + // rome-ignore lint/style/noNonNullAssertion: + const lastArg = pathCopy.pop()!; + + // The `path` ends up being something like `post.byId` + const path = pathCopy.join("."); + + if (lastArg === "useSWRMutation") { + return (hooks as any)[lastArg](path, ...args); + } + + const [input, ...rest] = args; + const queryKey = getQueryKey(path, input); + + /** + * Preload does not care about other opts. + */ + if (lastArg === "preload") { + return hooks.preload(queryKey); + } + + /** + * Make sure we error on improper usage + */ + if (lastArg === "getKey") { + const [unserialized] = rest; + if (typeof unserialized !== "boolean" && unserialized !== undefined) { + throw new Error("Expected second argument to be a boolean"); + } + hooks.getKey(queryKey, unserialized); + } + + return (hooks as any)[lastArg](queryKey, ...rest); + }); } export function createSWRProxyHooksInternal( - hooks: CreateTRPCSWRHooks, + hooks: CreateTRPCSWRHooks ) { - type CreateSWRInternalProxy = CreateTRPCSWRProxy; - - return createFlatProxy((key) => { - if (key in hooks) { - return hooks[key as keyof typeof hooks]; - } - return createSWRProxyDecoration(key, hooks); - }); + type CreateSWRInternalProxy = CreateTRPCSWRProxy; + + return createFlatProxy((key) => { + if (key in hooks) { + return hooks[key as keyof typeof hooks]; + } + return createSWRProxyDecoration(key, hooks); + }); } diff --git a/packages/client/src/shared/createSWRHooks.tsx b/packages/client/src/shared/createSWRHooks.tsx index dd632b8..6906274 100644 --- a/packages/client/src/shared/createSWRHooks.tsx +++ b/packages/client/src/shared/createSWRHooks.tsx @@ -1,31 +1,31 @@ import { - createTRPCClient, - CreateTRPCClientOptions, - createTRPCProxyClient, - TRPCClient, - TRPCClientErrorLike, + createTRPCClient, + CreateTRPCClientOptions, + createTRPCProxyClient, + TRPCClient, + TRPCClientErrorLike, } from "@trpc/client"; import { - AnyProcedure, - AnyRouter, - ClientDataTransformerOptions, - inferProcedureInput, - inferProcedureOutput, - ProcedureRouterRecord, + AnyProcedure, + AnyRouter, + ClientDataTransformerOptions, + inferProcedureInput, + inferProcedureOutput, + ProcedureRouterRecord, } from "@trpc/server"; import { PropsWithChildren, useContext, useEffect, useState } from "react"; import _useSWR, { - preload as _preload, - SWRConfig as _SWRConfig, - SWRConfiguration, - SWRResponse, - unstable_serialize, + preload as _preload, + SWRConfig as _SWRConfig, + SWRConfiguration, + SWRResponse, + unstable_serialize, } from "swr"; import { TRPCContext, TRPCContextType } from "./context"; import _useSWRMutation, { - SWRMutationConfiguration, - SWRMutationResponse, + SWRMutationConfiguration, + SWRMutationResponse, } from "swr/mutation"; import { CreateClient, GetQueryKey } from "./types"; import { useTransformFallback } from "./useTransformedFallback"; @@ -35,186 +35,186 @@ import { useTransformFallback } from "./useTransformedFallback"; * @deprecated - use `createSWRProxyHooks` instead */ export function createSWRHooks( - config: CreateTRPCClientOptions, + config: CreateTRPCClientOptions ): CreateTRPCSWRHooks { - // TODO - Infer types of useSWR - - const createClient: CreateClient = ( - configOverride?: CreateTRPCClientOptions, - ) => { - return createTRPCClient(configOverride || config); - }; - - const Context = TRPCContext as unknown as React.Context< - TRPCContextType - >; - - const useTRPCContext = () => useContext(Context); - - const useSWR = ( - pathAndInput: [string, any], - config?: SWRConfiguration & { - isDisabled?: boolean; - }, - ) => { - const { nativeClient } = useTRPCContext(); - const { isDisabled, ...swrConfig } = config || {}; - return _useSWR( - isDisabled ? null : pathAndInput, - (pathAndInput: ReturnType) => { - return (nativeClient as any).query(...pathAndInput); - }, - swrConfig, - ); - }; - - const useSWRMutation = ( - path: string, - config: SWRMutationConfiguration, - ) => { - const { nativeClient } = useTRPCContext(); - return _useSWRMutation( - path, - (path: any, { arg }: any) => { - return nativeClient.mutation(path, arg); - }, - config, - ); - }; - - let _clientRef: TRPCClient | null = null; - - const TRPCProvider = ({ - children, - client, - }: PropsWithChildren<{ client: TRPCClient }>) => { - useEffect(() => { - _clientRef = client; - }, [client]); - - const [vanillaClient] = useState(() => { - return createTRPCProxyClient(config); - }); - return ( - - {children} - - ); - }; - - const preload = >( - pathAndInput: PreloadData, - ) => { - /** - * SWR ??? - why is this not default? - */ - if (typeof window === "undefined") { - return Promise.resolve(); - } - - return _preload(pathAndInput, (pathAndInput: PreloadData) => { - // Create client instance if not already created (Will be Garbage collected once TRPCProvider mounts) - if (!_clientRef) { - _clientRef = createClient(); - } - return (_clientRef as any).query(...pathAndInput); - }); - }; - - const getKey = >( - pathAndInput: PreloadData, - unserialized = false, - ) => { - return unserialized ? pathAndInput : unstable_serialize(pathAndInput); - }; - - const SWRConfig: React.FC> = ({ - children, - value, - }) => { - const fallback = (value as any)?.fallback; - const transformedFallback = useTransformFallback( - fallback, - config?.transformer as any, - ); - - const finalValue = { - ...value, - }; - if (transformedFallback) { - (finalValue as any).fallback = transformedFallback; - } - return <_SWRConfig value={finalValue}>{children}; - }; - - return { - Provider: TRPCProvider, - useContext: useTRPCContext, - SWRConfig: SWRConfig, - useSWR, - useSWRMutation, - preload: preload, - getKey: getKey, - createClient, - } as any; + // TODO - Infer types of useSWR + + const createClient: CreateClient = ( + configOverride?: CreateTRPCClientOptions + ) => { + return createTRPCClient(configOverride || config); + }; + + const Context = TRPCContext as unknown as React.Context< + TRPCContextType + >; + + const useTRPCContext = () => useContext(Context); + + const useSWR = ( + pathAndInput: [string, any], + config?: SWRConfiguration & { + isDisabled?: boolean; + } + ) => { + const { nativeClient } = useTRPCContext(); + const { isDisabled, ...swrConfig } = config || {}; + return _useSWR( + isDisabled ? null : pathAndInput, + (pathAndInput: ReturnType) => { + return (nativeClient as any).query(...pathAndInput); + }, + swrConfig + ); + }; + + const useSWRMutation = ( + path: string, + config: SWRMutationConfiguration + ) => { + const { nativeClient } = useTRPCContext(); + return _useSWRMutation( + path, + (path: any, { arg }: any) => { + return nativeClient.mutation(path, arg); + }, + config + ); + }; + + let _clientRef: TRPCClient | null = null; + + const TRPCProvider = ({ + children, + client, + }: PropsWithChildren<{ client: TRPCClient }>) => { + useEffect(() => { + _clientRef = client; + }, [client]); + + const [vanillaClient] = useState(() => { + return createTRPCProxyClient(config); + }); + return ( + + {children} + + ); + }; + + const preload = >( + pathAndInput: PreloadData + ) => { + /** + * SWR ??? - why is this not default? + */ + if (typeof window === "undefined") { + return Promise.resolve(); + } + + return _preload(pathAndInput, (pathAndInput: PreloadData) => { + // Create client instance if not already created (Will be Garbage collected once TRPCProvider mounts) + if (!_clientRef) { + _clientRef = createClient(); + } + return (_clientRef as any).query(...pathAndInput); + }); + }; + + const getKey = >( + pathAndInput: PreloadData, + unserialized = false + ) => { + return unserialized ? pathAndInput : unstable_serialize(pathAndInput); + }; + + const SWRConfig: React.FC> = ({ + children, + value, + }) => { + const fallback = (value as any)?.fallback; + const transformedFallback = useTransformFallback( + fallback, + config?.transformer as any + ); + + const finalValue = { + ...value, + }; + if (transformedFallback) { + (finalValue as any).fallback = transformedFallback; + } + return <_SWRConfig value={finalValue}>{children}; + }; + + return { + Provider: TRPCProvider, + useContext: useTRPCContext, + SWRConfig: SWRConfig, + useSWR, + useSWRMutation, + preload: preload, + getKey: getKey, + createClient, + } as any; } export type TRPCProvider = React.FC< - React.PropsWithChildren<{ - client: TRPCClient; - }> + React.PropsWithChildren<{ + client: TRPCClient; + }> >; export type UseSWR<_ extends ProcedureRouterRecord> = < - TPath extends string, - TProcedure extends AnyProcedure, + TPath extends string, + TProcedure extends AnyProcedure >( - pathAndInput: [TPath, inferProcedureInput], - config?: SWRConfiguration & { - isDisabled?: boolean; - }, + pathAndInput: [TPath, inferProcedureInput], + config?: SWRConfiguration & { + isDisabled?: boolean; + } ) => SWRResponse< - inferProcedureOutput, - TRPCClientErrorLike + inferProcedureOutput, + TRPCClientErrorLike >; export type UseSWRMutation<_ extends ProcedureRouterRecord> = < - TPath extends string, - TProcedure extends AnyProcedure, - TInput = inferProcedureInput, - TOutput = inferProcedureOutput, + TPath extends string, + TProcedure extends AnyProcedure, + TInput = inferProcedureInput, + TOutput = inferProcedureOutput >( - path: TPath, - config: SWRMutationConfiguration, + path: TPath, + config: SWRMutationConfiguration ) => SWRMutationResponse< - TOutput, - TRPCClientErrorLike, - TInput, - TPath + TOutput, + TRPCClientErrorLike, + TPath, + TInput >; export interface CreateTRPCSWRHooks< - TRouter extends AnyRouter, - TProcedures extends ProcedureRouterRecord = TRouter["_def"]["record"], + TRouter extends AnyRouter, + TProcedures extends ProcedureRouterRecord = TRouter["_def"]["record"] > { - Provider: TRPCProvider; - SWRConfig: React.FC< - React.ComponentProps & { - transformer?: ClientDataTransformerOptions; - } - >; - useContext: () => TRPCContextType; - useSWR: UseSWR; - useSWRMutation: UseSWRMutation; - getKey: ( - pathAndInput: PreloadData, - unserialized?: boolean, - ) => string | PreloadData; - preload: (pathAndInput: ReturnType) => Promise; - createClient: CreateClient; + Provider: TRPCProvider; + SWRConfig: React.FC< + React.ComponentProps & { + transformer?: ClientDataTransformerOptions; + } + >; + useContext: () => TRPCContextType; + useSWR: UseSWR; + useSWRMutation: UseSWRMutation; + getKey: ( + pathAndInput: PreloadData, + unserialized?: boolean + ) => string | PreloadData; + preload: (pathAndInput: ReturnType) => Promise; + createClient: CreateClient; }