Skip to content

Commit 443d270

Browse files
LeCarbonatorBalastrongLuca
authored
feat(form-core): add <FieldMeta>.isValid (#1422)
* feat(form-core): add <FieldMeta>.isValid this is a shortened counterpart to checking field.state.meta.errors.length > 0 * docs: use isValid checks instaed of error length checks * fix: read field derived data from the right store * style: revert extracting errorMap variable in setMeta * style: revert extracting errorMap variable in setFieldMeta * docs: rephrase isValid description and related props --------- Co-authored-by: Leonardo Montini <[email protected]> Co-authored-by: Luca <[email protected]>
1 parent bfb8c42 commit 443d270

File tree

19 files changed

+128
-65
lines changed

19 files changed

+128
-65
lines changed

docs/framework/angular/guides/validation.md

Lines changed: 1 addition & 1 deletion
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>

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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 14 additions & 14 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 5 additions & 5 deletions
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/vue/guides/validation.md

Lines changed: 6 additions & 6 deletions
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

Lines changed: 1 addition & 1 deletion
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}

examples/lit/simple/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class TanStackFormDemo extends LitElement {
7070
/>
7171
</div>
7272
${field.state.meta.isTouched &&
73-
field.state.meta.errors.length
73+
!field.state.meta.isValid
7474
? html`${repeat(
7575
field.state.meta.errors,
7676
(__, idx) => idx,
@@ -167,7 +167,7 @@ export class TanStackFormDemo extends LitElement {
167167
/>
168168
</div>
169169
${jobTitleField.state.meta.isTouched &&
170-
jobTitleField.state.meta.errors.length
170+
!jobTitleField.state.meta.isValid
171171
? html`${repeat(
172172
jobTitleField.state.meta.errors,
173173
(__, idx) => idx,

examples/lit/ui-libraries/src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class TanStackFormDemo extends LitElement {
7373
}}"
7474
.error="${!!(
7575
field.state.meta.isTouched &&
76-
field.state.meta.errors.length
76+
!field.state.meta.isValid
7777
)}"
7878
.errorText="${field.state.meta.errors.join(', ')}"
7979
></md-filled-text-field>
@@ -96,7 +96,7 @@ export class TanStackFormDemo extends LitElement {
9696
}}"
9797
.error="${!!(
9898
lastNameField.state.meta.isTouched &&
99-
lastNameField.state.meta.errors.length
99+
!lastNameField.state.meta.isValid
100100
)}"
101101
.errorText="${lastNameField.state.meta.errors.join(
102102
', ',
@@ -147,8 +147,8 @@ export class TanStackFormDemo extends LitElement {
147147
e.target as HTMLInputElement
148148
jobTitleField.handleChange(target.value)
149149
}}"
150-
.error="${!!jobTitleField.state.meta
151-
.errors.length}"
150+
.error="${!jobTitleField.state.meta
151+
.isValid}"
152152
.errorText="${jobTitleField.state.meta.errors.join(
153153
', ',
154154
)}"

examples/react/compiler/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { AnyFieldApi } from '@tanstack/react-form'
66
function FieldInfo({ field }: { field: AnyFieldApi }) {
77
return (
88
<>
9-
{field.state.meta.isTouched && field.state.meta.errors.length ? (
9+
{field.state.meta.isTouched && !field.state.meta.isValid ? (
1010
<em>{field.state.meta.errors.join(',')}</em>
1111
) : null}
1212
{field.state.meta.isValidating ? 'Validating...' : null}

examples/react/field-errors-from-form-validators/src/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default function App() {
7878
field.handleChange(e.target.value)
7979
}}
8080
/>
81-
{field.state.meta.errors.length > 0 ? (
81+
{!field.state.meta.isValid ? (
8282
<em role="alert">{field.state.meta.errors.join(', ')}</em>
8383
) : null}
8484
</div>
@@ -100,7 +100,7 @@ export default function App() {
100100
type="number"
101101
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
102102
/>
103-
{field.state.meta.errors.length > 0 ? (
103+
{!field.state.meta.isValid ? (
104104
<em role="alert">{field.state.meta.errors.join(', ')}</em>
105105
) : null}
106106
</div>

examples/react/query-integration/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { AnyFieldApi } from '@tanstack/react-form'
1212
function FieldInfo({ field }: { field: AnyFieldApi }) {
1313
return (
1414
<>
15-
{field.state.meta.isTouched && field.state.meta.errors.length ? (
15+
{field.state.meta.isTouched && !field.state.meta.isValid ? (
1616
<em>{field.state.meta.errors.join(',')}</em>
1717
) : null}
1818
{field.state.meta.isValidating ? 'Validating...' : null}

examples/react/simple/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { AnyFieldApi } from '@tanstack/react-form'
66
function FieldInfo({ field }: { field: AnyFieldApi }) {
77
return (
88
<>
9-
{field.state.meta.isTouched && field.state.meta.errors.length ? (
9+
{field.state.meta.isTouched && !field.state.meta.isValid ? (
1010
<em>{field.state.meta.errors.join(',')}</em>
1111
) : null}
1212
{field.state.meta.isValidating ? 'Validating...' : null}

examples/react/standard-schema/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { AnyFieldApi } from '@tanstack/react-form'
1010
function FieldInfo({ field }: { field: AnyFieldApi }) {
1111
return (
1212
<>
13-
{field.state.meta.isTouched && field.state.meta.errors.length ? (
13+
{field.state.meta.isTouched && !field.state.meta.isValid ? (
1414
<em>{field.state.meta.errors.map((err) => err.message).join(',')}</em>
1515
) : null}
1616
{field.state.meta.isValidating ? 'Validating...' : null}

examples/solid/simple/src/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ interface FieldInfoProps {
1111
function FieldInfo(props: FieldInfoProps) {
1212
return (
1313
<>
14-
{props.field.state.meta.isTouched &&
15-
props.field.state.meta.errors.length ? (
14+
{props.field.state.meta.isTouched && !props.field.state.meta.isValid ? (
1615
<em>{props.field.state.meta.errors.join(',')}</em>
1716
) : null}
1817
{props.field.state.meta.isValidating ? 'Validating...' : null}

packages/form-core/src/FieldApi.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,10 @@ export type FieldMetaDerived<
652652
* A flag that is `true` if the field's value has not been modified by the user. Opposite of `isDirty`.
653653
*/
654654
isPristine: boolean
655+
/**
656+
* A boolean indicating if the field is valid. Evaluates `true` if there are no field errors.
657+
*/
658+
isValid: boolean
655659
}
656660

657661
export type AnyFieldMetaDerived = FieldMetaDerived<

0 commit comments

Comments
 (0)