question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Input is not being passed to mutation handler

See original GitHub issue

Apologies if I have missed something simple, but I cannot figure out why input is undefined in my mutation handler.

pages/api/trpc/[trpc].ts

import * as trpc from '@trpc/server'
import * as trpcNext from '@trpc/server/adapters/next'
import jwt from 'jsonwebtoken'
import superjson from 'superjson'
import { usersRouter } from '../../../routers/users'
import prisma from '../../../utils/prisma'

const createContext = async ({
	req,
	res,
}: trpcNext.CreateNextContextOptions) => {
	async function getUserFromHeader() {
		const authHeader = req.headers['authorization']
		const token = authHeader && authHeader.split(' ')[1]
		if (token == null) return null
		try {
			const payload = jwt.verify(token, process.env.JWT_SECRET)
			const user = await prisma.user.findFirst({
				where: { id: payload.id },
			})
			return user
		} catch (error) {
			return null
		}
	}

	const user = await getUserFromHeader()

	return {
		user,
	}
}

type Context = trpc.inferAsyncReturnType<typeof createContext>

export function createRouter() {
	return trpc.router<Context>()
}

const appRouter = createRouter()
	.transformer(superjson)
	.merge('users.', usersRouter)

export type AppRouter = typeof appRouter

export default trpcNext.createNextApiHandler({
	router: appRouter,
	createContext,
	onError({ error }) {
		console.log(error.message)
		if (error.code === 'INTERNAL_SERVER_ERROR') {
			console.error('Something went wrong', error)
		}
	},
	batching: {
		enabled: true,
	},
})

routers/users.ts

import * as trpc from '@trpc/server'
import argon2 from 'argon2'
import jwt from 'jsonwebtoken'
import { createRouter } from '../pages/api/trpc/[trpc]'
import { registerSchema } from '../types'
import prisma from '../utils/prisma'

export const usersRouter = createRouter().mutation('create', {
	input: registerSchema,
	async resolve({ input }) {
		console.log({ input })
		if (await prisma.user.findFirst({ where: { email: input.email } })) {
			throw trpc.httpError.badRequest('Email is taken')
		}
		const user = await prisma.user.create({
			data: {
				email: input.email,
				hashedPassword: await argon2.hash(input.password),
			},
		})
		const token = jwt.sign(
			{
				id: user.id,
			},
			process.env.JWT_SECRET
		)
		return {
			token,
		}
	},
})

types/index.ts

import { z } from 'zod'

export const registerSchema = z.object({
	email: z.string().email().min(8).max(160),
	password: z.string().min(12).max(200),
})

export type RegisterForm = z.infer<typeof registerSchema>

utils/trpc.ts

import { createReactQueryHooks } from '@trpc/react'
import type { AppRouter } from '../pages/api/trpc/[trpc]'

export const trpc = createReactQueryHooks<AppRouter>()

pages/_app.tsx

import { withTRPC } from '@trpc/next'
import { AppProps } from 'next/app'
import '../styles/globals.css'

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

export default withTRPC({
	config(_ctx) {
		const url = `${process.env.NEXT_PUBLIC_SERVER_URL}/api/trpc`

		return {
			url,
			queryClientConfig: {
				defaultOptions: {
					queries: {
						staleTime: 600,
					},
				},
			},
		}
	},
	ssr: true,
})(MyApp)

pages/register.tsx

import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import Layout from '../components/Layout'
import { RegisterForm, registerSchema } from '../types'
import { trpc } from '../utils/trpc'

const RegisterPage = () => {
	const createUser = trpc.useMutation('users.create')

	const {
		register,
		handleSubmit,
		formState: { errors },
	} = useForm<RegisterForm>({
		resolver: zodResolver(registerSchema),
	})

	const onSubmit = async (data: RegisterForm) => {
		await createUser.mutateAsync(data, {
			async onSuccess(res) {
				localStorage.setItem('token', res.token)
			},
		})
	}

	return (
		<Layout title="Register">
			<form onSubmit={handleSubmit(onSubmit)}>
				<div>
					<label>Email</label>
					<input {...register('email')} type="email" />
					<span>{errors.email?.message}</span>
				</div>

				<div>
					<label>Password</label>
					<input {...register('password')} type="password" />
					<span>{errors.password?.message}</span>
				</div>

				<div>
					<input type="submit" />
				</div>
			</form>
		</Layout>
	)
}

export default RegisterPage

Error

{
    "id": null,
    "error": {
        "json": {
            "message": "[\n  {\n    \"code\": \"invalid_type\",\n    \"expected\": \"object\",\n    \"received\": \"undefined\",\n    \"path\": [],\n    \"message\": \"Required\"\n  }\n]",
            "code": -32600,
            "data": {
                "code": "BAD_REQUEST",
                "stack": "TRPCError: [\n  {\n    \"code\": \"invalid_type\",\n    \"expected\": \"object\",\n    \"received\": \"undefined\",\n    \"path\": [],\n    \"message\": \"Required\"\n  }\n]\n    at ProcedureWithInput.parseInput (/Users/bytemagician/code/web/node_modules/@trpc/server/dist/router-175c3ce6.cjs.dev.js:37:13)\n    at ProcedureWithInput.call (/Users/bytemagician/code/web/node_modules/@trpc/server/dist/router-175c3ce6.cjs.dev.js:62:24)\n    at Router.invoke (/Users/bytemagician/code/web/node_modules/@trpc/server/dist/router-175c3ce6.cjs.dev.js:286:22)\n    at Object.mutation (/Users/bytemagician/code/web/node_modules/@trpc/server/dist/router-175c3ce6.cjs.dev.js:305:21)\n    at Object.callProcedure (webpack-internal:///./node_modules/@trpc/server/dist/callProcedure-66851817.cjs.dev.js:56:19)\n    at eval (webpack-internal:///./node_modules/@trpc/server/dist/index-5abbb896.cjs.dev.js:227:44)\n    at Array.map (<anonymous>)\n    at Object.requestHandler (webpack-internal:///./node_modules/@trpc/server/dist/index-5abbb896.cjs.dev.js:223:45)\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)\n    at async eval (webpack-internal:///./node_modules/@trpc/server/adapters/next/dist/trpc-server-adapters-next.cjs.dev.js:50:5)",
                "path": "users.create"
            }
        }
    }
}

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
ghostcommented, Jun 29, 2021

No worries.

I scaffolded a brand new Next.js app and copy-pasted the trpc.io quickstart snippets. The issue is now gone so it seems the error was on my end rather than the trpc framework.

It’s a bit confusing as I genuinely cannot find anything different between my original app and the vanilla one I just created.

Will go ahead and close.

0reactions
github-actions[bot]commented, Oct 5, 2022

This issue has been locked because it had no new activity for 14 days. If you are running into a similar issue, please create a new issue. Thank you.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Mutations in Apollo Client - Apollo GraphQL Docs
To execute a mutation, you first call useMutation within a React component and pass it the mutation you want to execute, like so:...
Read more >
Variables value not passed to GraphQL Mutation (React)
I am now trying to pass variables value to a mutation (frontend side) but I get the following error: Although, the mutation works...
Read more >
Validation and User Errors in GraphQL Mutations
In this blog post I would like to propose the pattern which you can use to handle user input validation and user errors...
Read more >
Input argument error paths in error response #298 - GitHub
The error handling through a mutation payload is limited to mutations. What about validation of input arguments on query or subscription ...
Read more >
GraphQL Mutations and Input Types - Episode #39
Explore naming conventions for input types, how to define them, and use them with variables. Mutations in GraphQL can look very different from ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found