@trpc/next
Version:
106 lines (103 loc) • 3.76 kB
JavaScript
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 };