From 3b92536dc4011e2d93be333dec8776e8533cb3d4 Mon Sep 17 00:00:00 2001 From: harshit-d3v Date: Sat, 1 Aug 2026 17:47:48 +0530 Subject: [PATCH 1/2] fix(solid-form): generate an SSR-safe default formId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `createForm` never passed a `formId` to `FormApi`, so it fell back to `uuid()`, which is seeded from `Math.random()` and therefore differs between the server render and the client render. Binding it (`
`) produced a hydration mismatch under SolidStart. Use Solid's `createUniqueId()` for the default instead, which is what the other adapters already do: - react-form/src/useFormId.ts — React.useId, falling back to useUUID on React 17 - preact-form/src/useFormId.ts — useId from preact/hooks - vue-form/src/useFormId.ts — Vue's useId, added in #2259 An explicitly provided `formId` is still preserved. Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/great-pugs-shave.md | 5 +++ packages/solid-form/src/createForm.tsx | 12 +++++-- .../solid-form/tests/createFormId.test.tsx | 36 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 .changeset/great-pugs-shave.md create mode 100644 packages/solid-form/tests/createFormId.test.tsx diff --git a/.changeset/great-pugs-shave.md b/.changeset/great-pugs-shave.md new file mode 100644 index 000000000..9bff8f521 --- /dev/null +++ b/.changeset/great-pugs-shave.md @@ -0,0 +1,5 @@ +--- +'@tanstack/solid-form': patch +--- + +Generate the default `formId` with Solid's `createUniqueId` so it is SSR-safe. Previously `createForm` never passed a `formId`, so `FormApi` fell back to a random uuid that differed between the server render and the client render, and binding it (``) produced a hydration mismatch under SolidStart. Mirrors the existing behaviour of the react, preact and vue adapters. diff --git a/packages/solid-form/src/createForm.tsx b/packages/solid-form/src/createForm.tsx index 01a216412..1a961f2a2 100644 --- a/packages/solid-form/src/createForm.tsx +++ b/packages/solid-form/src/createForm.tsx @@ -1,5 +1,5 @@ import { FormApi, functionalUpdate } from '@tanstack/form-core' -import { createComputed, onMount } from 'solid-js' +import { createComputed, createUniqueId, onMount } from 'solid-js' import { useSelector } from '@tanstack/solid-store' import { Field, createField } from './createField' import { FormGroup } from './createFormGroup' @@ -240,6 +240,14 @@ export function createForm< >, ) { const options = opts?.() + /** + * `FormApi` falls back to `uuid()` when no `formId` is given, which differs + * between the server render and the client render. `createUniqueId` is + * Solid's SSR-safe counterpart, so binding the id (``) + * no longer produces a hydration mismatch. Mirrors `useFormId` in the react, + * preact and vue adapters. + */ + const fallbackFormId = createUniqueId() const api = new FormApi< TParentData, TFormOnMount, @@ -253,7 +261,7 @@ export function createForm< TFormOnDynamicAsync, TFormOnServer, TSubmitMeta - >(options) + >({ ...options, formId: options?.formId ?? fallbackFormId }) const extendedApi: typeof api & SolidFormApi< TParentData, diff --git a/packages/solid-form/tests/createFormId.test.tsx b/packages/solid-form/tests/createFormId.test.tsx new file mode 100644 index 000000000..9b04e9b4a --- /dev/null +++ b/packages/solid-form/tests/createFormId.test.tsx @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest' +import { render } from '@solidjs/testing-library' +import { createForm } from '../src/index' + +function Comp() { + const form = createForm(() => ({ defaultValues: { firstName: '' } })) + return +} + +describe('createForm formId', () => { + it('uses the provided formId when one is given', () => { + function WithId() { + const form = createForm(() => ({ + defaultValues: { firstName: '' }, + formId: 'my-form', + })) + return + } + + const { getByTestId } = render(() => ) + + expect(getByTestId('form').getAttribute('id')).toBe('my-form') + }) + + it('derives the default formId from Solid so it survives hydration', () => { + // `form-core` falls back to `uuid()`, which is seeded from `Math.random()` + // and so differs between the server render and the client render. Solid's + // `createUniqueId()` is the adapter's SSR-safe counterpart: on the client + // it yields `cl-`, and under hydration it derives the id from the + // hydration context so both renders agree. Asserting the id comes from + // that sequence is what pins the default to the hydration-safe source. + const { getByTestId } = render(() => ) + + expect(getByTestId('form').getAttribute('id')).toMatch(/^cl-\d+$/) + }) +}) From c3d1e40157c96b84f52e78ef7fb9f727ba52eead Mon Sep 17 00:00:00 2001 From: harshit-d3v Date: Sat, 1 Aug 2026 18:12:35 +0530 Subject: [PATCH 2/2] docs(solid-form): correct changeset wording for the formId fallback An explicitly provided `formId` was already forwarded to `FormApi`; only the fallback was missing. Say that, and capitalize the framework names. Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/great-pugs-shave.md | 2 +- packages/solid-form/src/createForm.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/great-pugs-shave.md b/.changeset/great-pugs-shave.md index 9bff8f521..6b7151570 100644 --- a/.changeset/great-pugs-shave.md +++ b/.changeset/great-pugs-shave.md @@ -2,4 +2,4 @@ '@tanstack/solid-form': patch --- -Generate the default `formId` with Solid's `createUniqueId` so it is SSR-safe. Previously `createForm` never passed a `formId`, so `FormApi` fell back to a random uuid that differed between the server render and the client render, and binding it (``) produced a hydration mismatch under SolidStart. Mirrors the existing behaviour of the react, preact and vue adapters. +Generate the default `formId` with Solid's `createUniqueId` so it is SSR-safe. Previously, when no `formId` was configured, `createForm` did not provide a fallback, so `FormApi` generated a random UUID that differed between the server render and the client render. Binding that generated id (``) produced a hydration mismatch under SolidStart. An explicitly provided `formId` was already forwarded and is unchanged. This mirrors the existing behaviour of the React, Preact, and Vue adapters. diff --git a/packages/solid-form/src/createForm.tsx b/packages/solid-form/src/createForm.tsx index 1a961f2a2..e583bea73 100644 --- a/packages/solid-form/src/createForm.tsx +++ b/packages/solid-form/src/createForm.tsx @@ -244,8 +244,8 @@ export function createForm< * `FormApi` falls back to `uuid()` when no `formId` is given, which differs * between the server render and the client render. `createUniqueId` is * Solid's SSR-safe counterpart, so binding the id (``) - * no longer produces a hydration mismatch. Mirrors `useFormId` in the react, - * preact and vue adapters. + * no longer produces a hydration mismatch. Mirrors `useFormId` in the React, + * Preact, and Vue adapters. */ const fallbackFormId = createUniqueId() const api = new FormApi<