Skip to content

Commit e05fac4

Browse files
committed
feat(form-core): add <FieldMeta>.isValid
this is a shortened counterpart to checking field.state.meta.errors.length > 0
1 parent b5ea568 commit e05fac4

File tree

4 files changed

+191
-64
lines changed

4 files changed

+191
-64
lines changed

packages/form-core/src/FieldApi.ts

+71-35
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,10 @@ export type FieldMetaDerived<
642642
* A flag that is `true` if the field's value has not been modified by the user. Opposite of `isDirty`.
643643
*/
644644
isPristine: boolean
645+
/**
646+
* A boolean indicating if the field is valid.
647+
*/
648+
isValid: boolean
645649
}
646650

647651
export type AnyFieldMetaDerived = FieldMetaDerived<
@@ -1095,14 +1099,19 @@ export class FieldApi<
10951099
type: 'validate',
10961100
})
10971101
if (error) {
1098-
this.setMeta(
1099-
(prev) =>
1100-
({
1101-
...prev,
1102-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1103-
errorMap: { ...prev?.errorMap, onMount: error },
1104-
}) as never,
1105-
)
1102+
this.setMeta((prev) => {
1103+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1104+
const errorMap = { ...prev?.errorMap, onMount: error }
1105+
1106+
const fieldIsValid =
1107+
Object.values(errorMap).filter(Boolean).length === 0
1108+
1109+
return {
1110+
...prev,
1111+
errorMap,
1112+
isValid: fieldIsValid,
1113+
} as never
1114+
})
11061115
}
11071116
}
11081117

@@ -1367,15 +1376,22 @@ export class FieldApi<
13671376
: errorFromForm[errorMapKey]
13681377

13691378
if (field.state.meta.errorMap[errorMapKey] !== error) {
1370-
field.setMeta((prev) => ({
1371-
...prev,
1372-
errorMap: {
1379+
field.setMeta((prev) => {
1380+
const errorMap = {
13731381
...prev.errorMap,
13741382
[getErrorMapKey(validateObj.cause)]:
13751383
// Prefer the error message from the field validators if they exist
1376-
error ? error : errorFromForm[errorMapKey],
1377-
},
1378-
}))
1384+
error ?? errorFromForm[errorMapKey],
1385+
}
1386+
const fieldIsValid =
1387+
Object.values(errorMap).filter(Boolean).length === 0
1388+
1389+
return {
1390+
...prev,
1391+
errorMap,
1392+
isValid: fieldIsValid,
1393+
}
1394+
})
13791395
}
13801396
if (error || errorFromForm[errorMapKey]) {
13811397
hasErrored = true
@@ -1402,13 +1418,21 @@ export class FieldApi<
14021418
cause !== 'submit' &&
14031419
!hasErrored
14041420
) {
1405-
this.setMeta((prev) => ({
1406-
...prev,
1407-
errorMap: {
1421+
this.setMeta((prev) => {
1422+
const errorMap = {
14081423
...prev.errorMap,
14091424
[submitErrKey]: undefined,
1410-
},
1411-
}))
1425+
}
1426+
1427+
const fieldIsValid =
1428+
Object.values(errorMap).filter(Boolean).length === 0
1429+
1430+
return {
1431+
...prev,
1432+
errorMap,
1433+
isValid: fieldIsValid,
1434+
}
1435+
})
14121436
}
14131437

14141438
return { hasErrored }
@@ -1526,13 +1550,19 @@ export class FieldApi<
15261550
asyncFormValidationResults[this.name]?.[errorMapKey]
15271551
const fieldError = error || fieldErrorFromForm
15281552
field.setMeta((prev) => {
1553+
const errorMap = {
1554+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1555+
...prev?.errorMap,
1556+
[errorMapKey]: fieldError,
1557+
}
1558+
1559+
const fieldIsValid =
1560+
Object.values(errorMap).filter(Boolean).length === 0
1561+
15291562
return {
15301563
...prev,
1531-
errorMap: {
1532-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1533-
...prev?.errorMap,
1534-
[errorMapKey]: fieldError,
1535-
},
1564+
errorMap,
1565+
isValid: fieldIsValid,
15361566
}
15371567
})
15381568

@@ -1630,17 +1660,23 @@ export class FieldApi<
16301660
/**
16311661
* Updates the field's errorMap
16321662
*/
1633-
setErrorMap(errorMap: ValidationErrorMap) {
1634-
this.setMeta(
1635-
(prev) =>
1636-
({
1637-
...prev,
1638-
errorMap: {
1639-
...prev.errorMap,
1640-
...errorMap,
1641-
},
1642-
}) as never,
1643-
)
1663+
setErrorMap(errorMap: ValidationErrorMap, opts?: UpdateMetaOptions) {
1664+
this.setMeta((prev) => {
1665+
const newErrorMap = {
1666+
...prev.errorMap,
1667+
...errorMap,
1668+
}
1669+
1670+
const fieldIsValid = opts?.dontUpdateMeta
1671+
? prev.isValid
1672+
: Object.values(newErrorMap).filter(Boolean).length === 0
1673+
1674+
return {
1675+
...prev,
1676+
errorMap: newErrorMap,
1677+
isValid: fieldIsValid,
1678+
} as never
1679+
})
16441680
}
16451681

16461682
/**

packages/form-core/src/FormApi.ts

+58-28
Original file line numberDiff line numberDiff line change
@@ -828,12 +828,14 @@ export class FormApi<
828828
}
829829
}
830830

831-
// As a primitive, we don't need to aggressively persist the same referential value for performance reasons
831+
// As primitives, we don't need to aggressively persist the same referential value for performance reasons
832832
const isFieldPristine = !currBaseVal.isDirty
833+
const isFieldValid = !isNonEmptyArray(fieldErrors ?? [])
833834

834835
if (
835836
prevFieldInfo &&
836837
prevFieldInfo.isPristine === isFieldPristine &&
838+
prevFieldInfo.isValid === isFieldValid &&
837839
prevFieldInfo.errors === fieldErrors &&
838840
currBaseVal === prevBaseVal
839841
) {
@@ -846,6 +848,7 @@ export class FormApi<
846848
...currBaseVal,
847849
errors: fieldErrors,
848850
isPristine: isFieldPristine,
851+
isValid: isFieldValid,
849852
} as AnyFieldMeta
850853
}
851854

@@ -891,11 +894,9 @@ export class FormApi<
891894
(field) => field?.isValidating,
892895
)
893896

894-
const isFieldsValid = !fieldMetaValues.some(
895-
(field) =>
896-
field?.errorMap &&
897-
isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),
898-
)
897+
const isFieldsValid = fieldMetaValues
898+
.filter(Boolean)
899+
.every((field) => field!.isValid)
899900

900901
const isTouched = fieldMetaValues.some((field) => field?.isTouched)
901902
const isBlurred = fieldMetaValues.some((field) => field?.isBlurred)
@@ -1321,13 +1322,20 @@ export class FormApi<
13211322

13221323
const fieldMeta = this.getFieldMeta(field)
13231324
if (fieldMeta && fieldMeta.errorMap[errorMapKey] !== fieldError) {
1324-
this.setFieldMeta(field, (prev) => ({
1325-
...prev,
1326-
errorMap: {
1325+
this.setFieldMeta(field, (prev) => {
1326+
const errorMap = {
13271327
...prev.errorMap,
13281328
[errorMapKey]: fieldError,
1329-
},
1330-
}))
1329+
}
1330+
const fieldIsValid =
1331+
Object.values(errorMap).filter(Boolean).length === 0
1332+
1333+
return {
1334+
...prev,
1335+
errorMap,
1336+
isValid: fieldIsValid,
1337+
}
1338+
})
13311339
}
13321340
}
13331341
}
@@ -1345,13 +1353,20 @@ export class FormApi<
13451353
[errorMapKey]: undefined,
13461354
}
13471355

1348-
this.setFieldMeta(field, (prev) => ({
1349-
...prev,
1350-
errorMap: {
1356+
this.setFieldMeta(field, (prev) => {
1357+
const errorMap = {
13511358
...prev.errorMap,
13521359
[errorMapKey]: undefined,
1353-
},
1354-
}))
1360+
}
1361+
const fieldIsValid =
1362+
Object.values(errorMap).filter(Boolean).length === 0
1363+
1364+
return {
1365+
...prev,
1366+
isValid: fieldIsValid,
1367+
errorMap,
1368+
}
1369+
})
13551370
}
13561371
}
13571372

@@ -1483,13 +1498,20 @@ export class FormApi<
14831498
for (const [field, fieldError] of Object.entries(fieldErrors)) {
14841499
const fieldMeta = this.getFieldMeta(field as DeepKeys<TFormData>)
14851500
if (fieldMeta && fieldMeta.errorMap[errorMapKey] !== fieldError) {
1486-
this.setFieldMeta(field as DeepKeys<TFormData>, (prev) => ({
1487-
...prev,
1488-
errorMap: {
1501+
this.setFieldMeta(field as DeepKeys<TFormData>, (prev) => {
1502+
const errorMap = {
14891503
...prev.errorMap,
14901504
[errorMapKey]: fieldError,
1491-
},
1492-
}))
1505+
}
1506+
const fieldIsValid =
1507+
Object.values(errorMap).filter(Boolean).length === 0
1508+
1509+
return {
1510+
...prev,
1511+
errorMap,
1512+
isValid: fieldIsValid,
1513+
}
1514+
})
14931515
}
14941516
}
14951517
}
@@ -1753,16 +1775,24 @@ export class FormApi<
17531775

17541776
batch(() => {
17551777
if (!dontUpdateMeta) {
1756-
this.setFieldMeta(field, (prev) => ({
1757-
...prev,
1758-
isTouched: true,
1759-
isDirty: true,
1760-
errorMap: {
1778+
this.setFieldMeta(field, (prev) => {
1779+
const errorMap = {
17611780
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
17621781
...prev?.errorMap,
17631782
onMount: undefined,
1764-
},
1765-
}))
1783+
}
1784+
1785+
const fieldIsValid =
1786+
Object.values(errorMap).filter(Boolean).length === 0
1787+
1788+
return {
1789+
...prev,
1790+
isTouched: true,
1791+
isDirty: true,
1792+
errorMap,
1793+
isValid: fieldIsValid,
1794+
}
1795+
})
17661796
}
17671797

17681798
this.baseStore.setState((prev) => {

packages/form-core/src/metaHelper.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const defaultFieldMeta: AnyFieldMeta = {
1414
isBlurred: false,
1515
isDirty: false,
1616
isPristine: true,
17+
isValid: true,
1718
errors: [],
1819
errorMap: {},
1920
}

0 commit comments

Comments
 (0)