diff --git a/.changeset/dull-baboons-like.md b/.changeset/dull-baboons-like.md
new file mode 100644
index 000000000..bdc52fae0
--- /dev/null
+++ b/.changeset/dull-baboons-like.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/solid-form': patch
+---
+
+prevent full array re-renders in array mode
diff --git a/docs/framework/solid/guides/arrays.md b/docs/framework/solid/guides/arrays.md
index 52d9149b6..8c1b5240e 100644
--- a/docs/framework/solid/guides/arrays.md
+++ b/docs/framework/solid/guides/arrays.md
@@ -83,7 +83,7 @@ function App() {
form.handleSubmit()
}}
>
-
+
{(field) => (
0}>
diff --git a/examples/solid/array/src/index.tsx b/examples/solid/array/src/index.tsx
index e6beadce9..e930e712c 100644
--- a/examples/solid/array/src/index.tsx
+++ b/examples/solid/array/src/index.tsx
@@ -21,7 +21,7 @@ function App() {
form.handleSubmit()
}}
>
-
+
{(field) => (
0}>
diff --git a/packages/solid-form/src/createField.tsx b/packages/solid-form/src/createField.tsx
index 1284f0aaf..18ff73c70 100644
--- a/packages/solid-form/src/createField.tsx
+++ b/packages/solid-form/src/createField.tsx
@@ -18,7 +18,11 @@ import type {
} from '@tanstack/form-core'
import type { Accessor, JSX, JSXElement } from 'solid-js'
-import type { CreateFieldOptions, CreateFieldOptionsBound } from './types'
+import type {
+ CreateFieldOptions,
+ CreateFieldOptionsBound,
+ FieldOptionsMode,
+} from './types'
interface SolidFieldApi<
TParentData,
@@ -123,6 +127,7 @@ function makeFieldReactive<
TFormOnServer,
TParentSubmitMeta
>,
+ { mode }: FieldOptionsMode,
): () => FieldApi<
TParentData,
TName,
@@ -163,12 +168,50 @@ function makeFieldReactive<
TParentSubmitMeta
> {
const [field, setField] = createSignal(fieldApi, { equals: false })
- // Handle shallow comparison to make sure that Derived doesn't create a new setField call every time
- const store = useStore(fieldApi.store, (store) => store)
+ // Subscribe to the pieces of state that should trigger a re-render of the
+ // field. For array mode, we only track the length of the array value to
+ // avoid re-renders when child properties change. Meta is tracked piece by
+ // piece so that consumers re-render when any meta property updates.
+ // See: https://github.com/TanStack/form/issues/1961
+ const reactiveStateValue = useStore(fieldApi.store, (state) =>
+ mode === 'array'
+ ? Object.keys((state.value as unknown) ?? []).length
+ : state.value,
+ )
+ const reactiveMetaIsTouched = useStore(
+ fieldApi.store,
+ (state) => state.meta.isTouched,
+ )
+ const reactiveMetaIsBlurred = useStore(
+ fieldApi.store,
+ (state) => state.meta.isBlurred,
+ )
+ const reactiveMetaIsDirty = useStore(
+ fieldApi.store,
+ (state) => state.meta.isDirty,
+ )
+ const reactiveMetaErrorMap = useStore(
+ fieldApi.store,
+ (state) => state.meta.errorMap,
+ )
+ const reactiveMetaErrorSourceMap = useStore(
+ fieldApi.store,
+ (state) => state.meta.errorSourceMap,
+ )
+ const reactiveMetaIsValidating = useStore(
+ fieldApi.store,
+ (state) => state.meta.isValidating,
+ )
// Run before initial render
createComputed(() => {
- // Use the store to track dependencies
- store()
+ // Read all reactive sources to track them as dependencies
+ reactiveStateValue()
+ reactiveMetaIsTouched()
+ reactiveMetaIsBlurred()
+ reactiveMetaIsDirty()
+ reactiveMetaErrorMap()
+ reactiveMetaErrorSourceMap()
+ reactiveMetaIsValidating()
setField(fieldApi)
})
return field
@@ -285,7 +328,7 @@ export function createField<
TFormOnDynamicAsync,
TFormOnServer,
TParentSubmitMeta
- >(extendedApi as never)
+ >(extendedApi as never, { mode: options.mode })
}
interface FieldComponentBoundProps<
diff --git a/packages/solid-form/src/types.ts b/packages/solid-form/src/types.ts
index 21909578e..2d0807655 100644
--- a/packages/solid-form/src/types.ts
+++ b/packages/solid-form/src/types.ts
@@ -9,7 +9,10 @@ import type {
FormValidateOrFn,
} from '@tanstack/form-core'
-interface FieldOptionsMode {
+/**
+ * @private
+ */
+export interface FieldOptionsMode {
mode?: 'value' | 'array'
}
diff --git a/packages/solid-form/tests/createField.test.tsx b/packages/solid-form/tests/createField.test.tsx
index df2b444a1..a2a492010 100644
--- a/packages/solid-form/tests/createField.test.tsx
+++ b/packages/solid-form/tests/createField.test.tsx
@@ -567,4 +567,32 @@ describe('createField', () => {
await user.click(await findByText('Submit'))
expect(fn).toHaveBeenCalledWith({ people: [{ name: 'John', age: 0 }] })
})
+
+ it('should support array mode', async () => {
+ function Comp() {
+ const form = createForm(() => ({
+ defaultValues: {
+ test: ['a'],
+ },
+ }))
+
+ return (
+
+ {(field) => (
+
+
{JSON.stringify(field().state.value)}
+
+
+ )}
+
+ )
+ }
+
+ const { getByTestId, getByText } = render(() => )
+ expect(getByTestId('val')).toHaveTextContent('["a"]')
+ await user.click(getByText('push'))
+ await waitFor(() =>
+ expect(getByTestId('val')).toHaveTextContent('["a","b"]'),
+ )
+ })
})