UNPKG

@trpc/next

Version:

The tRPC Next.js library

106 lines (103 loc) 3.76 kB
import { createTRPCUntypedClient, TRPCClientError } from '@trpc/client'; import { getTransformer } from '@trpc/client/unstable-internals'; import { observable } from '@trpc/server/observable'; import { transformResult } from '@trpc/server/unstable-core-do-not-import'; import { useRef, useState, useEffect, useCallback, useMemo } from 'react'; import { isFormData } from './shared.mjs'; // ts-prune-ignore-next function experimental_serverActionLink(...args) { const [opts] = args; const transformer = getTransformer(opts?.transformer); return ()=>({ op })=>observable((observer)=>{ const context = op.context; context._action(isFormData(op.input) ? op.input : transformer.input.serialize(op.input)).then((data)=>{ const transformed = transformResult(data, transformer.output); if (!transformed.ok) { observer.error(TRPCClientError.from(transformed.error, {})); return; } observer.next({ context: op.context, result: transformed.result }); observer.complete(); }).catch((cause)=>{ observer.error(TRPCClientError.from(cause)); }); }); } // ts-prune-ignore-next function experimental_createActionHook(opts) { const client = createTRPCUntypedClient(opts); return function useAction(handler, useActionOpts) { const count = useRef(0); const [state, setState] = useState({ status: 'idle' }); const actionOptsRef = useRef(useActionOpts); actionOptsRef.current = useActionOpts; useEffect(()=>{ return ()=>{ // cleanup after unmount to prevent calling hook opts after unmount count.current = -1; actionOptsRef.current = undefined; }; }, []); const mutateAsync = useCallback((input, requestOptions)=>{ const idx = ++count.current; const context = { ...requestOptions?.context, _action (innerInput) { return handler(innerInput); } }; setState({ status: 'loading' }); return client.mutation('serverAction', input, { ...requestOptions, context }).then(async (data)=>{ await actionOptsRef.current?.onSuccess?.(data); if (idx !== count.current) { return; } setState({ status: 'success', data: data }); }).catch(async (error)=>{ await actionOptsRef.current?.onError?.(error); throw error; }).catch((error)=>{ if (idx !== count.current) { return; } setState({ status: 'error', error: TRPCClientError.from(error, {}) }); throw error; }); }, [ handler ]); const mutate = useCallback((...args)=>{ void mutateAsync(...args).catch(()=>{ // ignored }); }, [ mutateAsync ]); return useMemo(()=>({ ...state, mutate, mutateAsync }), [ mutate, mutateAsync, state ]); }; } export { experimental_createActionHook, experimental_serverActionLink };