Skip to content

Commit a0247f4

Browse files
committed
Merge branch 'main' of github.com:TanStack/form into feat-deep-keys-of-type
2 parents e076cd6 + 5565233 commit a0247f4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+3487
-2714
lines changed

docs/assets/field-states-extended.png

113 KB
Loading

docs/assets/field-states.png

-98.5 KB
Loading

docs/config.json

+4
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,10 @@
487487
{
488488
"label": "Balastrong's Tutorial",
489489
"to": "framework/react/community/balastrong-tutorial"
490+
},
491+
{
492+
"label": "Community Tutorials",
493+
"to": "framework/react/community/tutorials"
490494
}
491495
]
492496
}

docs/framework/angular/guides/validation.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ So you can control when the validation is done by implementing the desired callb
120120
(blur)="age.api.handleBlur()"
121121
(input)="age.api.handleChange($any($event).target.valueAsNumber)"
122122
/>
123-
@if (age.api.state.meta.errors) {
123+
@if (!age.api.state.meta.isValid) {
124124
<em role="alert">{{ age.api.state.meta.errors.join(', ') }}</em>
125125
}
126126
</ng-container>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
id: community-tutorials
3+
title: Community Tutorials
4+
---
5+
6+
This page is a collection of community-created tutorials, articles, and videos that can help you learn more about TanStack Form from other developers. If you created a resource that you would like to add to this list, please open a PR! We keep them in chronological order by publish date to ensure the most up to date content is at the top.
7+
8+
> Please note that the content listed here is entirely community maintained. While it may not be fully aligned with official recommendations and best practices, it can still offer valuable insights and alternative perspectives.
9+
10+
## TanStack Form Tutorial - Best Form Library for React?
11+
12+
[Watch Video](https://youtu.be/5oFQd-uAAHo) (March 7th, 2025)
13+
14+
A tutorial from [Atharva Deosthale](https://links.atharva.codes) using TanStack Form in a Next.js project. The video is made for people who are just getting started with knowing TanStack Form and will cover client-side form validation and server-side form validation by taking advantage of the Form SDK. This tutorial expects you to have basic knowledge of working of React and client-server architectures.

docs/framework/react/guides/basic-concepts.md

+27-4
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,40 @@ const {
9292
} = field.state
9393
```
9494

95-
There are three field states that can be useful to see how the user interacts with a field: A field is _"touched"_ when the user clicks/tabs into it, _"pristine"_ until the user changes value in it, and _"dirty"_ after the value has been changed. You can check these states via the `isTouched`, `isPristine` and `isDirty` flags, as seen below.
95+
There are three states in the metadata that can be useful to see how the user interacts with a field:
96+
97+
- _"isTouched"_, after the user clicks/tabs into the field
98+
- _"isPristine"_, until the user changes the field value
99+
- _"isDirty"_, after the fields value has been changed
96100

97101
```tsx
98102
const { isTouched, isPristine, isDirty } = field.state.meta
99103
```
100104

101105
![Field states](https://raw.githubusercontent.com/TanStack/form/main/docs/assets/field-states.png)
102106

103-
> **Important note for users coming from `React Hook Form`**: the `isDirty` flag in `TanStack/form` is different from the flag with the same name in RHF.
104-
> In RHF, `isDirty = true`, when the form's values are different from the original values. If the user changes the values in a form, and then changes them again to end up with values that match the form's default values, `isDirty` will be `false` in RHF, but `true` in `TanStack/form`.
105-
> The default values are exposed both on the form's and the field's level in `TanStack/form` (`form.options.defaultValues`, `field.options.defaultValue`), so you can write your own `isDefaultValue()` helper if you need to emulate RHF's behavior.`
107+
## Understanding 'isDirty' in Different Libraries
108+
109+
Non-Persistent `dirty` state
110+
111+
- **Libraries**: React Hook Form (RHF), Formik, Final Form.
112+
- **Behavior**: A field is 'dirty' if its value differs from the default. Reverting to the default value makes it 'clean' again.
113+
114+
Persistent `dirty` state
115+
116+
- **Libraries**: Angular Form, Vue FormKit.
117+
- **Behavior**: A field remains 'dirty' once changed, even if reverted to the default value.
118+
119+
We have chosen the persistent 'dirty' state model. To also support a non-persistent 'dirty' state, we introduce the isDefault flag. This flag acts as an inverse of the non-persistent 'dirty' state.
120+
121+
```tsx
122+
const { isTouched, isPristine, isDirty, isDefaultValue } = field.state.meta
123+
124+
// The following line will re-create the non-Persistent `dirty` functionality.
125+
const nonPersistentIsDirty = !isDefaultValue
126+
```
127+
128+
![Field states extended](https://raw.githubusercontent.com/TanStack/form/main/docs/assets/field-states-extended.png)
106129

107130
## Field API
108131

docs/framework/react/guides/react-native.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ Here is an example:
1919
<>
2020
<Text>Age:</Text>
2121
<TextInput value={field.state.value} onChangeText={field.handleChange} />
22-
{field.state.meta.errors ? (
22+
{!field.state.meta.isValid && (
2323
<Text>{field.state.meta.errors.join(', ')}</Text>
24-
) : null}
24+
)}
2525
</>
2626
)}
2727
</form.Field>

docs/framework/react/guides/validation.md

+14-14
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ Here is an example:
3333
type="number"
3434
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
3535
/>
36-
{field.state.meta.errors ? (
36+
{!field.state.meta.isValid && (
3737
<em role="alert">{field.state.meta.errors.join(', ')}</em>
38-
) : null}
38+
)}
3939
</>
4040
)}
4141
</form.Field>
@@ -64,9 +64,9 @@ In the example above, the validation is done at each keystroke (`onChange`). If,
6464
// We always need to implement onChange, so that TanStack Form receives the changes
6565
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
6666
/>
67-
{field.state.meta.errors ? (
67+
{!field.state.meta.isValid && (
6868
<em role="alert">{field.state.meta.errors.join(', ')}</em>
69-
) : null}
69+
)}
7070
</>
7171
)}
7272
</form.Field>
@@ -96,9 +96,9 @@ So you can control when the validation is done by implementing the desired callb
9696
// We always need to implement onChange, so that TanStack Form receives the changes
9797
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
9898
/>
99-
{field.state.meta.errors ? (
99+
{!field.state.meta.isValid && (
100100
<em role="alert">{field.state.meta.errors.join(', ')}</em>
101-
) : null}
101+
)}
102102
</>
103103
)}
104104
</form.Field>
@@ -122,9 +122,9 @@ Once you have your validation in place, you can map the errors from an array to
122122
return (
123123
<>
124124
{/* ... */}
125-
{field.state.meta.errors.length ? (
125+
{!field.state.meta.isValid && (
126126
<em>{field.state.meta.errors.join(',')}</em>
127-
) : null}
127+
)}
128128
</>
129129
)
130130
}}
@@ -273,9 +273,9 @@ export default function App() {
273273
type="number"
274274
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
275275
/>
276-
{field.state.meta.errors ? (
276+
{!field.state.meta.isValid && (
277277
<em role="alert">{field.state.meta.errors.join(', ')}</em>
278-
) : null}
278+
)}
279279
</>
280280
)}
281281
</form.Field>
@@ -357,9 +357,9 @@ To do this, we have dedicated `onChangeAsync`, `onBlurAsync`, and other methods
357357
type="number"
358358
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
359359
/>
360-
{field.state.meta.errors ? (
360+
{!field.state.meta.isValid && (
361361
<em role="alert">{field.state.meta.errors.join(', ')}</em>
362-
) : null}
362+
)}
363363
</>
364364
)}
365365
</form.Field>
@@ -389,9 +389,9 @@ Synchronous and Asynchronous validations can coexist. For example, it is possibl
389389
onBlur={field.handleBlur}
390390
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
391391
/>
392-
{field.state.meta.errors ? (
392+
{!field.state.meta.isValid && (
393393
<em role="alert">{field.state.meta.errors.join(', ')}</em>
394-
) : null}
394+
)}
395395
</>
396396
)}
397397
</form.Field>

docs/framework/react/quick-start.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ const PeoplePage = () => {
120120
type="number"
121121
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
122122
/>
123-
{field.state.meta.errors.length ? (
123+
{!field.state.meta.isValid && (
124124
<em>{field.state.meta.errors.join(',')}</em>
125-
) : null}
125+
)}
126126
</>
127127
)}
128128
/>

docs/framework/solid/guides/validation.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Here is an example:
3333
type="number"
3434
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
3535
/>
36-
{field().state.meta.errors ? (
36+
{!field().state.meta.isValid ? (
3737
<em role="alert">{field().state.meta.errors.join(', ')}</em>
3838
) : null}
3939
</>
@@ -64,7 +64,7 @@ In the example above, the validation is done at each keystroke (`onChange`). If,
6464
// We always need to implement onInput, so that TanStack Form receives the changes
6565
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
6666
/>
67-
{field().state.meta.errors ? (
67+
{!field().state.meta.isValid ? (
6868
<em role="alert">{field().state.meta.errors.join(', ')}</em>
6969
) : null}
7070
</>
@@ -96,7 +96,7 @@ So you can control when the validation is done by implementing the desired callb
9696
// We always need to implement onInput, so that TanStack Form receives the changes
9797
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
9898
/>
99-
{field().state.meta.errors ? (
99+
{!field().state.meta.isValid ? (
100100
<em role="alert">{field().state.meta.errors.join(', ')}</em>
101101
) : null}
102102
</>
@@ -122,7 +122,7 @@ Once you have your validation in place, you can map the errors from an array to
122122
return (
123123
<>
124124
{/* ... */}
125-
{field().state.meta.errors.length ? (
125+
{!field().state.meta.isValid ? (
126126
<em>{field().state.meta.errors.join(',')}</em>
127127
) : null}
128128
</>
@@ -364,7 +364,7 @@ To do this, we have dedicated `onChangeAsync`, `onBlurAsync`, and other methods
364364
type="number"
365365
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
366366
/>
367-
{field().state.meta.errors ? (
367+
{!field().state.meta.isValid ? (
368368
<em role="alert">{field().state.meta.errors.join(', ')}</em>
369369
) : null}
370370
</>

docs/framework/solid/reference/functions/usestore.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ title: useStore
1313
function useStore<TState, TSelected>(store, selector?): Accessor<TSelected>
1414
```
1515

16-
Defined in: node\_modules/.pnpm/@tanstack+solid-store@0.7.0\_solid-js@1.9.5/node\_modules/@tanstack/solid-store/dist/esm/index.d.ts:8
16+
Defined in: node\_modules/.pnpm/@tanstack+solid-store@0.7.0\_solid-js@1.9.6/node\_modules/@tanstack/solid-store/dist/esm/index.d.ts:8
1717

1818
### Type Parameters
1919

@@ -41,7 +41,7 @@ Defined in: node\_modules/.pnpm/@[email protected]\[email protected]/node
4141
function useStore<TState, TSelected>(store, selector?): Accessor<TSelected>
4242
```
4343

44-
Defined in: node\_modules/.pnpm/@tanstack+solid-store@0.7.0\_solid-js@1.9.5/node\_modules/@tanstack/solid-store/dist/esm/index.d.ts:9
44+
Defined in: node\_modules/.pnpm/@tanstack+solid-store@0.7.0\_solid-js@1.9.6/node\_modules/@tanstack/solid-store/dist/esm/index.d.ts:9
4545

4646
### Type Parameters
4747

docs/framework/vue/guides/validation.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Here is an example:
3535
@input="(e) => field.handleChange((e.target as HTMLInputElement).valueAsNumber)
3636
"
3737
/>
38-
<em role="alert" v-if="field.state.meta.errors">{{
38+
<em role="alert" v-if="!field.state.meta.isValid">{{
3939
field.state.meta.errors.join(', ')
4040
}}</em>
4141
</template>
@@ -69,7 +69,7 @@ In the example above, the validation is done at each keystroke (`onChange`). If,
6969
@input="(e) => field.handleChange((e.target as HTMLInputElement).valueAsNumber)
7070
"
7171
/>
72-
<em role="alert" v-if="field.state.meta.errors">{{
72+
<em role="alert" v-if="!field.state.meta.isValid">{{
7373
field.state.meta.errors.join(', ')
7474
}}</em>
7575
</template>
@@ -104,7 +104,7 @@ So you can control when the validation is done by implementing the desired callb
104104
@input="(e) => field.handleChange((e.target as HTMLInputElement).valueAsNumber)
105105
"
106106
/>
107-
<em role="alert" v-if="field.state.meta.errors">{{
107+
<em role="alert" v-if="!field.state.meta.isValid">{{
108108
field.state.meta.errors.join(', ')
109109
}}</em>
110110
</template>
@@ -131,7 +131,7 @@ Once you have your validation in place, you can map the errors from an array to
131131
>
132132
<template v-slot="{ field }">
133133
<!-- ... -->
134-
<em role="alert" v-if="field.state.meta.errors">{{
134+
<em role="alert" v-if="!field.state.meta.isValid">{{
135135
field.state.meta.errors.join(', ')
136136
}}</em>
137137
</template>
@@ -346,7 +346,7 @@ const onChangeAge = async ({ value }) => {
346346
field.handleChange((e.target as HTMLInputElement).valueAsNumber)
347347
"
348348
/>
349-
<em role="alert" v-if="field.state.meta.errors">{{
349+
<em role="alert" v-if="!field.state.meta.isValid">{{
350350
field.state.meta.errors.join(', ')
351351
}}</em>
352352
</template>
@@ -391,7 +391,7 @@ const onBlurAgeAsync = async ({ value }) => {
391391
field.handleChange((e.target as HTMLInputElement).valueAsNumber)
392392
"
393393
/>
394-
<em role="alert" v-if="field.state.meta.errors">{{
394+
<em role="alert" v-if="!field.state.meta.isValid">{{
395395
field.state.meta.errors.join(', ')
396396
}}</em>
397397
</template>

docs/overview.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import type { AnyFieldApi } from '@tanstack/react-form'
3434
function FieldInfo({ field }: { field: AnyFieldApi }) {
3535
return (
3636
<>
37-
{field.state.meta.isTouched && field.state.meta.errors.length ? (
37+
{field.state.meta.isTouched && !field.state.meta.isValid ? (
3838
<em>{field.state.meta.errors.join(', ')}</em>
3939
) : null}
4040
{field.state.meta.isValidating ? 'Validating...' : null}

0 commit comments

Comments
 (0)