tRPC sockets with react

Trying to make my React app work with socket with minimal server but getting error: "Uncaught TypeError: Cannot destructure property 'client' of 'useContext(...)' as it is null." at Object.useSubscription (createHooksInternal-9c1f8ad9.mjs:363:17) at createHooksInternal-9c1f8ad9.mjs:57:29 Current code on comments!
R
rocawear470d ago
Server:
import { inferAsyncReturnType, initTRPC } from "@trpc/server";
import {
CreateHTTPContextOptions,
createHTTPServer,
} from "@trpc/server/adapters/standalone";
import {
CreateWSSContextFnOptions,
applyWSSHandler,
} from "@trpc/server/adapters/ws";
import { observable } from "@trpc/server/observable";
import ws from "ws";
// import { z } from "zod";

// This is how you initialize a context for the server
function createContext(
opts: CreateHTTPContextOptions | CreateWSSContextFnOptions
) {
return {};
}
type Context = inferAsyncReturnType<typeof createContext>;

const t = initTRPC.context<Context>().create();

const publicProcedure = t.procedure;
const router = t.router;

const postRouter = router({
randomNumber: publicProcedure.subscription(() => {
return observable<{ randomNumber: number }>((emit) => {
const timer = setInterval(() => {
emit.next({ randomNumber: Math.random() });
console.log({ randomNumber: Math.random() });
}, 200);

return () => {
clearInterval(timer);
};
});
}),
});

// Merge routers together
export const appRouter = router({
post: postRouter,
});

export type AppRouter = typeof appRouter;

// http server
const { server, listen } = createHTTPServer({
router: appRouter,
createContext,
});

// ws server
const wss = new ws.Server({ server });
applyWSSHandler<AppRouter>({
wss,
router: appRouter,
createContext,
});

setInterval(() => {
console.log("Connected clients", wss.clients.size);
}, 1000);
listen(2022);
console.log("Server running");
import { inferAsyncReturnType, initTRPC } from "@trpc/server";
import {
CreateHTTPContextOptions,
createHTTPServer,
} from "@trpc/server/adapters/standalone";
import {
CreateWSSContextFnOptions,
applyWSSHandler,
} from "@trpc/server/adapters/ws";
import { observable } from "@trpc/server/observable";
import ws from "ws";
// import { z } from "zod";

// This is how you initialize a context for the server
function createContext(
opts: CreateHTTPContextOptions | CreateWSSContextFnOptions
) {
return {};
}
type Context = inferAsyncReturnType<typeof createContext>;

const t = initTRPC.context<Context>().create();

const publicProcedure = t.procedure;
const router = t.router;

const postRouter = router({
randomNumber: publicProcedure.subscription(() => {
return observable<{ randomNumber: number }>((emit) => {
const timer = setInterval(() => {
emit.next({ randomNumber: Math.random() });
console.log({ randomNumber: Math.random() });
}, 200);

return () => {
clearInterval(timer);
};
});
}),
});

// Merge routers together
export const appRouter = router({
post: postRouter,
});

export type AppRouter = typeof appRouter;

// http server
const { server, listen } = createHTTPServer({
router: appRouter,
createContext,
});

// ws server
const wss = new ws.Server({ server });
applyWSSHandler<AppRouter>({
wss,
router: appRouter,
createContext,
});

setInterval(() => {
console.log("Connected clients", wss.clients.size);
}, 1000);
listen(2022);
console.log("Server running");
Client:
import { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { splitLink, createWSClient, wsLink, httpBatchLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import { appRouter } from "../../server/index";
import { useMemo } from "react";

function App() {
const [queryClient] = useState(() => new QueryClient());
const [data, setData] = useState<{ randomNumber: number }>();
const trpc = createTRPCReact<typeof appRouter>();

const trpcClient = useMemo(() => {
const wsClient = createWSClient({ url: "ws://localhost:2022/trpc" });
return trpc.createClient({
links: [
splitLink({
condition: (op) => op.type === "subscription",
true: wsLink({ client: wsClient }),
false: httpBatchLink({ url: "http://localhost:2022/trpc" }),
}),
],
});
}, []);

const subscription = trpc.post.randomNumber.useSubscription(undefined, {
onData(data) {
console.log("received", data);
setData(data);
},
onError(err) {
console.error("error", err);
},
});

return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<>
<h1>Hello</h1>
{data}
</>
</QueryClientProvider>
</trpc.Provider>
);
}

export default App;
import { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { splitLink, createWSClient, wsLink, httpBatchLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import { appRouter } from "../../server/index";
import { useMemo } from "react";

function App() {
const [queryClient] = useState(() => new QueryClient());
const [data, setData] = useState<{ randomNumber: number }>();
const trpc = createTRPCReact<typeof appRouter>();

const trpcClient = useMemo(() => {
const wsClient = createWSClient({ url: "ws://localhost:2022/trpc" });
return trpc.createClient({
links: [
splitLink({
condition: (op) => op.type === "subscription",
true: wsLink({ client: wsClient }),
false: httpBatchLink({ url: "http://localhost:2022/trpc" }),
}),
],
});
}, []);

const subscription = trpc.post.randomNumber.useSubscription(undefined, {
onData(data) {
console.log("received", data);
setData(data);
},
onError(err) {
console.error("error", err);
},
});

return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<>
<h1>Hello</h1>
{data}
</>
</QueryClientProvider>
</trpc.Provider>
);
}

export default App;
If I remove this part it renders content but obv does not work so the error comes from here:
const subscription = trpc.post.randomNumber.useSubscription(undefined, {
onData(data) {
console.log("received", data);
setData(data);
},
onError(err) {
console.error("error", err);
},
});
const subscription = trpc.post.randomNumber.useSubscription(undefined, {
onData(data) {
console.log("received", data);
setData(data);
},
onError(err) {
console.error("error", err);
},
});
N
Nick470d ago
const [queryClient] = useState(() => new QueryClient()); const trpc = createTRPCReact<typeof appRouter>(); Probably not the cause but it would be better practice to move these out of the component, you're just creating possible headaches, like createTRPCReact blowing away any state that exists in there on each render pass
R
rocawear470d ago
Yep, I actually moved those there just to make it clearer. I have util/trpc.ts from where I normally would use it
N
Nick470d ago
Okay, well trpc.post.randomNumber.useSubscription will want to use the context API to get the query client and trpc, but it's being called above them in the component heirarchy Most apps have a Main.tsx and an App.tsx If you put all your providers in Main, and render App inside Main, then make your calls within App, it will get the context correctly
N
R
rocawear470d ago
Hey thanks! It works! 🙂
R
Rammstein386d ago
Hi I am facing similar problem for a long time now. Can you guys please help me out? Following are the files utils/trpc.ts
import { httpBatchLink, loggerLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import superjson from 'superjson';



import type { AppRouter } from "../server/routers/_app";
import SuperJSON from "superjson";

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

export const trpc = createTRPCNext<AppRouter>({
config({ ctx }) {
return {
transformer: superjson,
links: [
httpBatchLink({
url: getBaseUrl() + '/api/trpc',
}),
],
};
},

ssr: false,
});
import { httpBatchLink, loggerLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import superjson from 'superjson';



import type { AppRouter } from "../server/routers/_app";
import SuperJSON from "superjson";

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

export const trpc = createTRPCNext<AppRouter>({
config({ ctx }) {
return {
transformer: superjson,
links: [
httpBatchLink({
url: getBaseUrl() + '/api/trpc',
}),
],
};
},

ssr: false,
});
__app.tsx
import '../styles/globals.css';
import type {AppProps, AppType} from 'next/app';
import Theme from '../styles/Theme';
import { trpc } from '../utils/trpc';

const MyApp:AppType=({ Component, pageProps }: AppProps)=> {
return (
<Theme>
<Component {...pageProps} />
</Theme>
);
}

export default trpc.withTRPC(MyApp);
import '../styles/globals.css';
import type {AppProps, AppType} from 'next/app';
import Theme from '../styles/Theme';
import { trpc } from '../utils/trpc';

const MyApp:AppType=({ Component, pageProps }: AppProps)=> {
return (
<Theme>
<Component {...pageProps} />
</Theme>
);
}

export default trpc.withTRPC(MyApp);
trpc/[trpc].ts
import * as trpcNext from '@trpc/server/adapters/next';
import { createContext } from '../../../server/context';
import { appRouter } from '../../../server/routers/_app';

export default trpcNext.createNextApiHandler({
router: appRouter,
createContext,
});
import * as trpcNext from '@trpc/server/adapters/next';
import { createContext } from '../../../server/context';
import { appRouter } from '../../../server/routers/_app';

export default trpcNext.createNextApiHandler({
router: appRouter,
createContext,
});
trpc.ts
import { Context } from "./context";
import {initTRPC, TRPCError} from '@trpc/server';
import superjson from 'superjson';
import { OpenApiMeta } from 'trpc-openapi';


const t = initTRPC.context<Context>().meta<OpenApiMeta>().create({
transformer: superjson,
errorFormatter({ shape }) {
return shape;
},
});

export const router = t.router;
export const publicProcedure = t.procedure;

const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.user) {
throw new TRPCError({message:"UNAUTHORIZED",code: 'UNAUTHORIZED' });
}
return next({
ctx: {
user: ctx.user,
},
});
});

export const hasCloudmate = t.middleware(({next,ctx})=>{
if (!ctx.cloudmateUserObject || !ctx.cloudmateAsanaObject) {
throw new TRPCError({message:"CLOUDMATE NOT COMPLETELY SETUP",code: "PRECONDITION_FAILED" });
}
if(!ctx.ownerUserObject || !ctx.ownerAsanaObject){
throw new TRPCError({message:"CREATOR USER NOT COMPLETELY SETUP",code: "PRECONDITION_FAILED" });
}
return next();
});
// you can reuse this for any procedure
export const protectedProcedure = t.procedure.use(isAuthed);

export const middleware = t.middleware;
export const mergeRouters = t.mergeRouters;
import { Context } from "./context";
import {initTRPC, TRPCError} from '@trpc/server';
import superjson from 'superjson';
import { OpenApiMeta } from 'trpc-openapi';


const t = initTRPC.context<Context>().meta<OpenApiMeta>().create({
transformer: superjson,
errorFormatter({ shape }) {
return shape;
},
});

export const router = t.router;
export const publicProcedure = t.procedure;

const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.user) {
throw new TRPCError({message:"UNAUTHORIZED",code: 'UNAUTHORIZED' });
}
return next({
ctx: {
user: ctx.user,
},
});
});

export const hasCloudmate = t.middleware(({next,ctx})=>{
if (!ctx.cloudmateUserObject || !ctx.cloudmateAsanaObject) {
throw new TRPCError({message:"CLOUDMATE NOT COMPLETELY SETUP",code: "PRECONDITION_FAILED" });
}
if(!ctx.ownerUserObject || !ctx.ownerAsanaObject){
throw new TRPCError({message:"CREATOR USER NOT COMPLETELY SETUP",code: "PRECONDITION_FAILED" });
}
return next();
});
// you can reuse this for any procedure
export const protectedProcedure = t.procedure.use(isAuthed);

export const middleware = t.middleware;
export const mergeRouters = t.mergeRouters;
this is just a test page i am using /pages/test/trpc-test.tsx
import { trpc } from "../../utils/trpc";
import { useEffect, useState } from "react";

const TestPage = () => {
const [userData, setUserData] = useState<any>();
const [orgData, setOrgData] = useState<any>();

const { data: users } = trpc.user.listUsers.useQuery();
const { data: organizations } = trpc.workspace.getWorkspaces.useQuery({});
console.log("Orgs: ", organizations);
useEffect(() => {
setUserData(users);
setOrgData(organizations);
console.log("user state", userData);
}, [users, userData, organizations, orgData]);
if (!users) return;
return (
<div>
</div>
);
};
export default TestPage;
import { trpc } from "../../utils/trpc";
import { useEffect, useState } from "react";

const TestPage = () => {
const [userData, setUserData] = useState<any>();
const [orgData, setOrgData] = useState<any>();

const { data: users } = trpc.user.listUsers.useQuery();
const { data: organizations } = trpc.workspace.getWorkspaces.useQuery({});
console.log("Orgs: ", organizations);
useEffect(() => {
setUserData(users);
setOrgData(organizations);
console.log("user state", userData);
}, [users, userData, organizations, orgData]);
if (!users) return;
return (
<div>
</div>
);
};
export default TestPage;
More Posts
Should useQueries be able to 'select' data?Hi! I'm trying to use the newly implemented `useQueries` method to query for multiple items. I need Right way to structure your code when using tRPC?I was wondering how you properly structure your tRPC ruter code? Writing all the code in the routersReact Native UsageHello, I wanted to know if trpc can be used with react native ? And is it possible on a bare react ntransformers, tensor.js, PyTorch and tRPCdumb question: does anyone has experience with tensorflow.js? is there any major obstacle to use tensubscriptionHow do you guys Authenticate / Authorize Subscription ?NextJS and createProxySSGHelpers context type errorHi guys, do you guys know a better way of clean this typescript error? createProxySSGHelpers({ Validating PermissionsHi! A common operation that I'm doing in tRPC is validating that a person has permissions to perforAny typescript chads able know if it's possible to map a type to another type w genericsNot 100% sure if this is appropriate to ask here but I figured there's a lot of good TS developers ouseQuery enabled not working???Even setting the `enabled` to false --> `trpc.order.get({id: "123"}, {enabled: false})` still make`QueryClientProvider` not included in `withTRPC`?Trying to use `import { useIsMutating } from "react-query"` but it's throwing `Error: No QueryClientHandle React error boundarySeems like Im doing something wrong as I can't handle the error boundary from trpc query. I've queryAny tips for Typescript slowness?We successfully migrated from 9.x to 10 but are still seeing slow VS Code perf (10+ seconds to get aChange status of useMutation from 'success' back to 'idle'Hiya, I have a mutation for creating stuff that can run a few different ways on a page, and I want tIs it possible to call one procedure in another?I have multiple routers in my trpc server. For example userRouter (e.g. procedure: getUser) and postHandling Query Errors at Root of App (v9)I want to show an error toast on my `NextJS` frontend every time there is an error from `useQuery` itRPC Client webpack errorAs soon as I add the client part to my legacy app i get an error and Can't figure out what is wrong.cookies, headers and authenticationin express I can do something like `res.cookie("name", "value")` for example. alternatively I can dremove queryis there any api on the trpc client to instantly remove the query out of the query cache? Basicallytrpc hook pattern. This works, but I’m not convinced…I want to call my route when a button is clicked and have access to isLoading, onError etc… I have i