Arxk
Arxkโ€ข4w ago

Sending FormData does not work at all

https://github.com/trpc/trpc/blob/main/examples/minimal-content-types I have this exact same setup with tRPC but does not work. Server just says "Input not instance of FormData", looking at the network tab it just sends a json payload
GitHub
trpc/examples/minimal-content-types at main ยท trpc/trpc
๐Ÿง™โ€โ™€๏ธ Move Fast and Break Nothing. End-to-end typesafe APIs made easy. - trpc/trpc
Solution:
Got it working. Had to use splitLink with a condition for isNonJsonSerializable
Jump to solution
11 Replies
Nick
Nickโ€ข4w ago
Your setup must not be exactly the same then What link setup are you using? What does your code look like when submitting the data?
Arxk
ArxkOPโ€ข4w ago
I've been looking at this issue: https://github.com/trpc/trpc/issues/1937#issuecomment-2557358137 and it's kind of solved the problem, looks like I needed to use a custom solution for the transformer But now I just get errors:
Arxk
ArxkOPโ€ข4w ago
"use client";

import { QueryClientProvider, type QueryClient } from "@tanstack/react-query";
import { httpBatchLink, isNonJsonSerializable, loggerLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import { useState } from "react";
import SuperJSON from "superjson";
import { createQueryClient } from "./query-client";

import type { inferRouterInputs, inferRouterOutputs, TRPCCombinedDataTransformer } from "@trpc/server";
import type { AppRouter } from "~/server/api/root";

let clientQueryClientSingleton: QueryClient | undefined = undefined;
const getQueryClient = () => {
if (typeof window === "undefined") {
// Server: always make a new query client
return createQueryClient();
}
// Browser: use singleton pattern to keep the same query client
return (clientQueryClientSingleton ??= createQueryClient());
};

export const api = createTRPCReact<AppRouter>();

/**
* Inference helper for inputs.
*
* @example type HelloInput = RouterInputs['example']['hello']
*/
export type RouterInputs = inferRouterInputs<AppRouter>;

/**
* Inference helper for outputs.
*
* @example type HelloOutput = RouterOutputs['example']['hello']
*/
export type RouterOutputs = inferRouterOutputs<AppRouter>;

// https://github.com/trpc/trpc/issues/1937#issuecomment-2557358137
export const transformer: TRPCCombinedDataTransformer = {
input: {
serialize: (obj) => {
if (isNonJsonSerializable(obj)) {
return obj;
} else {
return SuperJSON.serialize(obj);
}
},
deserialize: (obj) => {
if (isNonJsonSerializable(obj)) {
return obj;
} else {
return SuperJSON.deserialize(obj);
}
},
},
output: SuperJSON,
};

export function TRPCReactProvider(props: { children: React.ReactNode }) {
const queryClient = getQueryClient();

const [trpcClient] = useState(() =>
api.createClient({
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
httpBatchLink({
transformer,
url: getBaseUrl() + "/api/trpc",
headers: () => {
const headers = new Headers();
headers.set("x-trpc-source", "nextjs-react");
return headers;
},
})
],
})
);

return (
<QueryClientProvider client={queryClient}>
<api.Provider
client={trpcClient}
queryClient={queryClient}
>
{props.children}
</api.Provider>
</QueryClientProvider>
);
}

function getBaseUrl() {
if (typeof window !== "undefined") return window.location.origin;
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
return `http://127.0.0.1:${process.env.PORT ?? 3000}`;
}
"use client";

import { QueryClientProvider, type QueryClient } from "@tanstack/react-query";
import { httpBatchLink, isNonJsonSerializable, loggerLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import { useState } from "react";
import SuperJSON from "superjson";
import { createQueryClient } from "./query-client";

import type { inferRouterInputs, inferRouterOutputs, TRPCCombinedDataTransformer } from "@trpc/server";
import type { AppRouter } from "~/server/api/root";

let clientQueryClientSingleton: QueryClient | undefined = undefined;
const getQueryClient = () => {
if (typeof window === "undefined") {
// Server: always make a new query client
return createQueryClient();
}
// Browser: use singleton pattern to keep the same query client
return (clientQueryClientSingleton ??= createQueryClient());
};

export const api = createTRPCReact<AppRouter>();

/**
* Inference helper for inputs.
*
* @example type HelloInput = RouterInputs['example']['hello']
*/
export type RouterInputs = inferRouterInputs<AppRouter>;

/**
* Inference helper for outputs.
*
* @example type HelloOutput = RouterOutputs['example']['hello']
*/
export type RouterOutputs = inferRouterOutputs<AppRouter>;

// https://github.com/trpc/trpc/issues/1937#issuecomment-2557358137
export const transformer: TRPCCombinedDataTransformer = {
input: {
serialize: (obj) => {
if (isNonJsonSerializable(obj)) {
return obj;
} else {
return SuperJSON.serialize(obj);
}
},
deserialize: (obj) => {
if (isNonJsonSerializable(obj)) {
return obj;
} else {
return SuperJSON.deserialize(obj);
}
},
},
output: SuperJSON,
};

export function TRPCReactProvider(props: { children: React.ReactNode }) {
const queryClient = getQueryClient();

const [trpcClient] = useState(() =>
api.createClient({
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
httpBatchLink({
transformer,
url: getBaseUrl() + "/api/trpc",
headers: () => {
const headers = new Headers();
headers.set("x-trpc-source", "nextjs-react");
return headers;
},
})
],
})
);

return (
<QueryClientProvider client={queryClient}>
<api.Provider
client={trpcClient}
queryClient={queryClient}
>
{props.children}
</api.Provider>
</QueryClientProvider>
);
}

function getBaseUrl() {
if (typeof window !== "undefined") return window.location.origin;
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
return `http://127.0.0.1:${process.env.PORT ?? 3000}`;
}
Server:
const t = initTRPC.context<typeof createTRPCContext>().create({
transformer,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
const t = initTRPC.context<typeof createTRPCContext>().create({
transformer,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
Here's my packages:
"@trpc/client": "11.0.0-rc.730",
"@trpc/next": "11.0.0-rc.730",
"@trpc/react-query": "11.0.0-rc.730",
"@trpc/server": "11.0.0-rc.730",
"@trpc/client": "11.0.0-rc.730",
"@trpc/next": "11.0.0-rc.730",
"@trpc/react-query": "11.0.0-rc.730",
"@trpc/server": "11.0.0-rc.730",
Didn't know TRPC 11 is stable now Yeah not working on 11.0.0 stable
Solution
Arxk
Arxkโ€ข4w ago
Got it working. Had to use splitLink with a condition for isNonJsonSerializable
Json ๐Ÿ‘บ
Json ๐Ÿ‘บโ€ข3w ago
Hey, would you mind posting your specific solution using splitLink? I am having a hard time implementing this. I wish the docs explained some of this now that this feature is released.
jmac
jmacโ€ข3w ago
Same here
Nick
Nickโ€ข2w ago
Hi all, I'm working on updating the docs for this, but hopefully this fragment helps in the mean-time:
import {
httpBatchLink,
httpLink,
isNonJsonSerializable,
splitLink,
} from '@trpc/client';

splitLink({
condition: (op) => isNonJsonSerializable(op.input),
true: httpLink({
url,
}),
false: httpBatchLink({
url,
}),
})
import {
httpBatchLink,
httpLink,
isNonJsonSerializable,
splitLink,
} from '@trpc/client';

splitLink({
condition: (op) => isNonJsonSerializable(op.input),
true: httpLink({
url,
}),
false: httpBatchLink({
url,
}),
})
Carlos Ziegler
Carlos Zieglerโ€ข2w ago
Works fine to me ๐Ÿ™‚ I will ask if we have an complete exmaple with octetInputParser ๐Ÿ™‚
Nick
Nickโ€ข2w ago
Yes there is a minimal-content-types example in the repo
jmac
jmacโ€ข2w ago
Unfortunately I seem to be getting an issue reproducing the same example in the repo https://discord.com/channels/867764511159091230/1353965054235639850

Did you find this page helpful?