"A component is changing a controlled input to be uncontrolled" in Next.js App router
jakst opened this issue ยท comments
Describe the bug
When setting up the most barebones form with tanstack form in Next.js with the app router, I get the below error. It works fine with the pages router however.
Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
at input
at Field (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@tanstack+react-form@0.9.0_react-dom@18.2.0_react@18.2.0/node_modules/@tanstack/react-form/build/modern/useField.js:44:3)
at form
at Provider (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@tanstack+react-form@0.9.0_react-dom@18.2.0_react@18.2.0/node_modules/@tanstack/react-form/build/modern/useForm.js:23:95)
at UserForm (webpack-internal:///(app-pages-browser)/./app/form.tsx:12:79)
at main
at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:240:11)
at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:72:9)
at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:80:11)
at NotFoundErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/not-found-boundary.js:54:9)
at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/not-found-boundary.js:62:11)
at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:345:11)
at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/error-boundary.js:130:11)
at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:151:9)
at ScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:226:11)
at RenderFromTemplateContext (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/render-from-template-context.js:15:44)
at OuterLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/layout-router.js:355:11)
at body
at html
at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:72:9)
at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/redirect-boundary.js:80:11)
at NotFoundErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/not-found-boundary.js:54:9)
at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/not-found-boundary.js:62:11)
at DevRootNotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/dev-root-not-found-boundary.js:32:11)
at ReactDevOverlay (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:66:9)
at HotReload (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:295:11)
at Router (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/app-router.js:169:11)
at ErrorBoundaryHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/error-boundary.js:100:9)
at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/error-boundary.js:130:11)
at AppRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/components/app-router.js:451:13)
at ServerRoot (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/app-index.js:128:11)
at RSCComponent
at Root (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@14.0.3_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/client/app-index.js:144:11)
This is with this very simple form
"use client"
import { useForm } from "@tanstack/react-form"
export function UserForm() {
const form = useForm({
defaultValues: {
name: "John Doe",
},
})
return (
<form.Provider>
<form>
<form.Field name="name">
{(field) => (
<input
name={field.name}
value={field.state.value}
onChange={(event) => {
field.handleChange(event.target.value)
}}
/>
)}
</form.Field>
</form>
</form.Provider>
)
}
Your minimal, reproducible example
https://github.com/jakst/tanstack-form-error-message
Steps to reproduce
- Clone repo, install deps and run
pnpm dev
- Visit
http://localhost:3000
(or whichever port it started on) - Open the console and notice the error message described above has been printed
- Click the link at the bottom that takes you to the same form but in the pages router, notice it does not print an error message to the console
Expected behavior
I expect the form to not generate any errors
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
- MacOS 14.1.1
- Chrome v119
Tanstack Form adapter
react-form
TanStack Form version
v0.9.0
TypeScript version
No response
Additional context
No response
A temporary workaround is to only render the children if !== undefined
{(field) => (field.state.value !== undefined && (<><input ... /></>))}
...
value={field.state.value ?? ""}
...
Yeah that works for now. Good idea, thanks! But looking forward to not having to do that ๐
... value={field.state.value ?? ""} ...
This approach will cause issues when validating.
I think I've solved this issue ๐ I'm waiting to release TanStack Form 0.12 until I can figure out how our SSR/SSG API will work with Remix, but expect this fix to be rolled out alongside some fun Next.js-specific APIs (Namely, Server Action support)