vpjm
vpjm3w ago

FormData TRPCClientError

Hi i have this error in my client :
createFormControl.ts:1217 Uncaught (in promise) TRPCClientError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"url"
],
"message": "Required"
},
{
"code": "custom",
"message": "Input not instance of File",
"fatal": true,
"path": [
"file"
]
}
]
at TRPCClientError.from (TRPCClientError.mjs:48:20)
at httpBatchStreamLink.mjs:118:56
createFormControl.ts:1217 Uncaught (in promise) TRPCClientError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"url"
],
"message": "Required"
},
{
"code": "custom",
"message": "Input not instance of File",
"fatal": true,
"path": [
"file"
]
}
]
at TRPCClientError.from (TRPCClientError.mjs:48:20)
at httpBatchStreamLink.mjs:118:56
I am working with Next 15.2.2 and tRPC 11.0.0-rc.682. NO MATTER what I try, FormData cannot be sent to my server — this is very frustrating. If anybody has encountered this problem, please help me! I have a smaller project with the exact same uploading form, and it works fine there (even with latest next version) . I don’t know how to debug this — I’ve spent hours and haven’t found any major differences between the codebases. //DATA TRANSFORMER src: https://github.com/juliangra/trpc-next-formdata-app-router
interface DataTransformer {
serialize: (object: any) => any
deserialize: (object: any) => any
}

export class FormDataTransformer implements DataTransformer {
serialize(object: any) {
if (!(object instanceof FormData)) {
throw new Error('Expected FormData')
}

return object
}

deserialize(object: any) {
return object as JSON
}
}
interface DataTransformer {
serialize: (object: any) => any
deserialize: (object: any) => any
}

export class FormDataTransformer implements DataTransformer {
serialize(object: any) {
if (!(object instanceof FormData)) {
throw new Error('Expected FormData')
}

return object
}

deserialize(object: any) {
return object as JSON
}
}
1 Reply
vpjm
vpjmOP3w ago
/**
* Creates context for an incoming request
* @see https://trpc.io/docs/v11/context
*/
export const createTRPCContext = async (opts: FetchCreateContextFnOptions) => {
const session = await auth();

return {
session,
};
};

export type Context = Awaited<ReturnType<typeof createTRPCContext>>;
/**
* Creates context for an incoming request
* @see https://trpc.io/docs/v11/context
*/
export const createTRPCContext = async (opts: FetchCreateContextFnOptions) => {
const session = await auth();

return {
session,
};
};

export type Context = Awaited<ReturnType<typeof createTRPCContext>>;
//NEXT-API : api/trpc/[trpc]/route.ts
const handler = (req: NextRequest) =>
fetchRequestHandler({
router: appRouter,
req,
endpoint: '/api/trpc',
/**
* @see https://trpc.io/docs/v11/context
*/
createContext: createTRPCContext,
/**
* @see https://trpc.io/docs/v11/error-handling
*/
onError: process.env.NODE_ENV === "development" ? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
);
}
: undefined,
});

export { handler as GET, handler as POST };
const handler = (req: NextRequest) =>
fetchRequestHandler({
router: appRouter,
req,
endpoint: '/api/trpc',
/**
* @see https://trpc.io/docs/v11/context
*/
createContext: createTRPCContext,
/**
* @see https://trpc.io/docs/v11/error-handling
*/
onError: process.env.NODE_ENV === "development" ? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
);
}
: undefined,
});

export { handler as GET, handler as POST };
//PS : createQueryClient for TRPC PROVIDER ```typescript export const createQueryClient = () => new QueryClient({ defaultOptions: { queries: { // With SSR, we usually want to set some default staleTime // above 0 to avoid refetching immediately on the client staleTime: 30 * 1000, }, dehydrate: { serializeData: transformer.serialize, shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === "pending", }, hydrate: { deserializeData: transformer.deserialize, }, }, });``` //CLIENT FORM ```typescript const url = /test/upload/${uuidv4()} const formData = new FormData(); formData.append('file', data.image); formData.append('metadata', JSON.stringify({ filename, type:"IMAGE", extension, img_width: width, img_height: height, ..._.pick(file,["size","name"]), video_length:null, mimeType:"IMAGE_JPEG" })); formData.append('url', url); await createPost.mutateAsync(formData)``` //TRPC PROVIDER ```typescript function TRPCProviders(props: Readonly<{ children: React.ReactNode }>) { const queryClient = getQueryClient(); const [trpcClient] = useState(() => trpc.createClient({ links: [ loggerLink({ enabled: (op) => process.env.NODE_ENV === "development" || (op.direction === "down" && op.result instanceof Error), }), splitLink({ condition: (op) => op.type === 'subscription', true: unstable_httpSubscriptionLink({ url: getUrl(), /** * @see https://trpc.io/docs/v11/data-transformers */ transformer, }), false: unstable_httpBatchStreamLink({ url: getUrl(), /** * @see https://trpc.io/docs/v11/data-transformers */ transformer, }), }) ], }), );``` //ENDPOINT TRPC ```typescript const uploadFileSchema = zfd.formData({ url: zfd.text(), file: zfd.file(z.instanceof(File)), metadata: zfd.json(z.any()), }); export const appRouter = createTRPCRouter({ uploadFile: publicProcedure.input(uploadFileSchema).mutation(async (opts) => { console.log(opts.input); })}) ``` Now I have the same endpoint and frontend as https://github.com/trpc/examples-next-formdata, but it still doesn't work. ```typescript export function Upload() { const mutation = trpc.myEndpoint.create.useMutation({ onError(err) { alert('Error from server: ' + err.message); }, }); const schema = zfd.formData({ name: zfd.text(), image: zfd.file(), }); const form = useZodFormData({ schema, }); const [noJs, setNoJs] = useState(false); return ( <> <FormProvider {...form}> <form method="post" action={/api/trpc/${mutation.trpc.path}`} encType="multipart/form-data" onSubmit={(_event) => { if (noJs) { return; } void form.handleSubmit(async (values, event) => { await mutation.mutateAsync(new FormData(event?.target)); })(_event); }} style={{ display: 'flex', flexDirection: 'column', gap: 10 }} ref={form.formRef} > <fieldset> <div style={{}}> <label htmlFor="name">Enter your name</label> <input {...form.register('name')} /> {form.formState.errors.name && ( <div>{form.formState.errors.name.message}</div> )} </div> <div> <label>Required file, only images</label> <input type="file" {...form.register('image')} /> {form.formState.errors.image && ( <div>{form.formState.errors.image.message}</div> )} </div> <div> <button type="submit" disabled={mutation.status === 'pending'}> submit </button> </div> </fieldset> </form> </FormProvider> </> ); }
// ENDPOINT TRPC
// ENDPOINT TRPC
typescript const uploadFileSchema = zfd.formData({ name: zfd.text(), image: zfd.file(), }); export const appRouter = createTRPCRouter({ //... myEndpoint: publicProcedure.input(uploadFileSchema).mutation(async (opts) => { console.log(opts.input); }) })
As Nick Lucas advised me, I went back to the basics
As Nick Lucas advised me, I went back to the basics
typescript //QueryClient export const createQueryClient = () => new QueryClient({ defaultOptions: { queries: { // With SSR, we usually want to set some default staleTime // above 0 to avoid refetching immediately on the client staleTime: 30 * 1000, }, }}) //PROVIDER function TRPCProviders(props: Readonly<{ children: React.ReactNode }>) { const queryClient = getQueryClient(); const [trpcClient] = useState(() => trpc.createClient({ links: [ loggerLink({ enabled: (op) => process.env.NODE_ENV === "development" (op.direction === "down" && op.result instanceof Error), }), splitLink({ condition: (op) => op.type === 'subscription', true: unstable_httpSubscriptionLink({ url: getUrl(), / * @see https://trpc.io/docs/v11/data-transformers */ transformer, }), false: unstable_httpBatchStreamLink({ url: getUrl(), / * @see https://trpc.io/docs/v11/data-transformers */ transformer, }), }), ], }), ); return ( <QueryClientProvider client={getQueryClient()}> <trpc.Provider client={trpcClient} queryClient={queryClient}> {props.children} </trpc.Provider> <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ); }
but still dont work
My stack is next 15.2.2 , trpc v11 and next auth (idk if this is important)
I try to simplify again but :
but still dont work
My stack is next 15.2.2 , trpc v11 and next auth (idk if this is important)
I try to simplify again but :
Uncaught (in promise) TRPCClientError: [ { "code": "invalid_type", "expected": "string", "received": "undefined", "path": [ "name" ], "message": "Required" }, { "code": "custom", "message": "Input not instance of File", "fatal": true, "path": [ "image" ] } ]
look like the same error
THIS : https://discord.com/channels/867764511159091230/1336316873620455515
solution :
look like the same error
THIS : https://discord.com/channels/867764511159091230/1336316873620455515
solution :
typescript // TRPC PROVIDER file export class FormDataTransformer implements DataTransformer { serialize(object: any) { if (!(object instanceof FormData)) { throw new Error("Expected FormData"); } return object; } deserialize(object: any) { return object as JSON; } } function TRPCProviders(props: Readonly<{ children: React.ReactNode }>) { const queryClient = getQueryClient(); const [trpcClient] = useState(() => trpc.createClient({ links: [ loggerLink({ enabled: (op) => process.env.NODE_ENV === "development"
(op.direction === "down" && op.result instanceof Error), }), splitLink({ condition: (op) => !isNonJsonSerializable(op.input) && op.type !== "subscription" && !op.context["stream"], true: httpBatchLink({url: getUrl(),transformer }), false: splitLink({ condition: (op) => isNonJsonSerializable(op.input) && op.type !== "subscription" && !op.context["stream"], true: httpLink({ url: getUrl(),transformer: new FormDataTransformer(), }), false: splitLink({ condition: (op) => op.type === "subscription" && !op.context["stream"], true: unstable_httpSubscriptionLink({ url: getUrl(),transformer }), false: unstable_httpBatchStreamLink({ url: getUrl(),transformer }), }), }), }), ], }), ); return ( <QueryClientProvider client={getQueryClient()}> <trpc.Provider client={trpcClient} queryClient={queryClient}> {props.children} </trpc.Provider> <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ); }``` /solve

Did you find this page helpful?