Reputation: 202
I have issues creating a story of a component that is using trpc. Storybook is running, but when I open the story I get the following error:
Cannot destructure property 'client' of 'useContext(...)' as it is null. TypeError: Cannot destructure property 'client' of 'useContext(...)' as it is null. at Object.useMutation$1 [as useMutation] (http://localhost:6006/vendors-node_modules_babel_runtime_helpers_esm_defineProperty_js-node_modules_trpc_next_dist_-afeaa2.iframe.bundle.js:6209:17) at http://localhost:6006/vendors-node_modules_babel_runtime_helpers_esm_defineProperty_js-node_modules_trpc_next_dist_-afeaa2.iframe.bundle.js:5884:34 at Object.apply (http://localhost:6006/vendors-node_modules_babel_runtime_helpers_esm_defineProperty_js-node_modules_trpc_next_dist_-afeaa2.iframe.bundle.js:6583:20) at PostSettings (http://localhost:6006/stories-components-feed-postPreview-HeaderPostPreview-stories.iframe.bundle.js:401:92) at renderWithHooks (http://localhost:6006/vendors-node_modules_babel_runtime_helpers_esm_asyncToGenerator_js-node_modules_babel_runtime-86a76a.iframe.bundle.js:83400:18) at mountIndeterminateComponent (http://localhost:6006/vendors-node_modules_babel_runtime_helpers_esm_asyncToGenerator_js-node_modules_babel_runtime-86a76a.iframe.bundle.js:87164:13) at beginWork (http://localhost:6006/vendors-node_modules_babel_runtime_helpers_esm_asyncToGenerator_js-node_modules_babel_runtime-86a76a.iframe.bundle.js:88677:16) at beginWork$1 (http://localhost:6006/vendors-node_modules_babel_runtime_helpers_esm_asyncToGenerator_js-node_modules_babel_runtime-86a76a.iframe.bundle.js:94516:14) at performUnitOfWork (http://localhost:6006/vendors-node_modules_babel_runtime_helpers_esm_asyncToGenerator_js-node_modules_babel_runtime-86a76a.iframe.bundle.js:93647:12) at workLoopSync (http://localhost:6006/vendors-node_modules_babel_runtime_helpers_esm_asyncToGenerator_js-node_modules_babel_runtime-86a76a.iframe.bundle.js:93556:5)
@utils/trpc - comes from t3-app
import { httpBatchLink, loggerLink } from '@trpc/client'
import { createTRPCNext } from '@trpc/next'
import { type inferRouterInputs, type inferRouterOutputs } from '@trpc/server'
import superjson from 'superjson'
import { IN_DEV } from '@constants/app'
import { type AppRouter } from '@server/trpc/router/_app'
const getBaseUrl = () => {
if (typeof window !== 'undefined') return '' // browser should use relative url
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}` // SSR should use vercel url
return `http://localhost:${process.env.PORT ?? 3000}` // dev SSR should use localhost
}
export const trpc = createTRPCNext<AppRouter>({
config() {
return {
transformer: superjson,
links: [
loggerLink({
enabled: (opts) =>
IN_DEV ||
(opts.direction === 'down' && opts.result instanceof Error),
}),
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
}
},
ssr: false,
})
/**
* 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>
PostPreview.stories.tsx
import React from 'react'
import type { StoryFn, Meta } from '@storybook/react'
import { trpc } from '@utils/trpc'
const PostPreview = () => {
const { data, status } = trpc.post.getAll.useQuery()
if (status === 'loading') {
return <p>loading</p>
}
if (status === 'error' || !data) {
return <p>error</p>
}
return <div>{JSON.stringify(data)}</div>
}
export default {
title: 'Library/PostPreview',
component: PostPreview,
argTypes: {},
} as Meta<typeof PostPreview>
const Template: StoryFn<typeof PostPreview> = () => ( <PostPreview />
)
export const Standard = Template.bind({})
Upvotes: 1
Views: 1272
Reputation: 202
got it working with the help of testerez solution
@stories/mocks/trpc
/* eslint-disable react/display-name */
import type { PropsWithChildren } from 'react'
import { useState } from 'react'
import { createTRPCReact, httpBatchLink } from '@trpc/react-query'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import superjson from 'superjson'
import type { AppRouter } from '../../server/trpc/router/_app'
import { getBaseUrl } from '@utils/trpc'
export const mockedTrpc = createTRPCReact<AppRouter>()
export const StorybookTrpcProvider = ({ children }: PropsWithChildren) => {
const [queryClient] = useState(
new QueryClient({ defaultOptions: { queries: { staleTime: Infinity } } })
)
const [trpcClient] = useState(() =>
mockedTrpc.createClient({
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
transformer: superjson,
})
)
return (
<mockedTrpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</mockedTrpc.Provider>
)
}
type TrpcContext = ReturnType<(typeof mockedTrpc)['useContext']>
// Hack to be able to access trpcContext
const ActOnTrpcContext = ({
callback,
children,
}: PropsWithChildren<{
callback: (trpcContext: TrpcContext) => void
}>) => {
const trpcContext = mockedTrpc.useContext()
callback(trpcContext)
return <>{children}</>
}
export const withTrpcContext =
(callback: (context: TrpcContext) => void) => (Story: React.FC) =>
(
<ActOnTrpcContext callback={callback}>
<Story />
</ActOnTrpcContext>
)
MyStory.stories.tsx
import type { Meta } from '@storybook/react'
import {
mockedTrpc,
StorybookTrpcProvider,
withTrpcContext,
} from '@stories/mocks/trpc'
// The component to be showcased
const Post = () => {
const query = mockedTrpc.post.get.useQuery('123')
return (
<>
<div>Status: {query.status}</div>
<pre>ID: {query.data.id}</pre>
</>
)
}
const meta: Meta<typeof Post> = {
title: 'Post',
component: Post,
decorators: [
(Story) => (
<StorybookTrpcProvider>
<Story />
</StorybookTrpcProvider>
),
],
}
export default meta
export const UsdWithNoPlugins = {
render: () => <Post />,
decorators: [
withTrpcContext((ctx) => {
ctx.post.get.setData('123', () => {
return {
id: '123',
}
})
}),
],
}
Upvotes: 0