Skip to content

Commit

Permalink
feat: Allow returning anything in a validator (#1104)
Browse files Browse the repository at this point in the history
* chore: WIP adding in inferencing to validator's return type

* chore: more work on fixing types

* chore: wip on things more

* chore: fix issues with FormApi and FieldApi

* chore: add more fields to infer

* chore: change typed from unknown to undefined

* chore: more fixes

* chore: add some new type tests

* chore: add more type tests

* ci: apply automated fixes and generate docs

* chore: migrate React components to use new prop types

* chore: move useTransform hook to new types

* chore: add convinience type for AnyFormAPI and AnyFieldAPI

* ci: apply automated fixes and generate docs

* chore: fix NextJS and Remix adapters

* chore: fix build of Start adapter

* ci: apply automated fixes and generate docs

* ci: apply automated fixes and generate docs

* chore: fix issues with Nx on Windows

* chore: fix issues with FieldAny in examples

* chore: upgrade React packages to stable

* chore: WIP attempt to fix Start package

* chore: fix mergeForm typing

* ci: apply automated fixes and generate docs

* chore: migrate Angular adapter to use new API

* ci: apply automated fixes and generate docs

* chore: migrate Lit adapter to new generics

* ci: apply automated fixes and generate docs

* chore: attempt 1 at fixing Vue's types

* chore: fix Vue types for JSX and Vue SFCs alike

* chore: migrate Vue type to use new generics

* chore: fix Vue test types

* chore: fix Vue examples

* ci: apply automated fixes and generate docs

* chore: migrate Solid to new generics system

* chore: fix Solid examples

* ci: apply automated fixes and generate docs

* ci: apply automated fixes and generate docs (attempt 2/3)

* chore: fix issues with Solid library

* chore: fix knip

* chore: fix issues with error casting

* chore: fix issue with ESLint

* docs: show errorMap and errorarray

* chore: upgrade all deps

* ci: apply automated fixes and generate docs

* ci: apply automated fixes and generate docs

* chore: address PR feedback

* ci: apply automated fixes and generate docs

* chore: fix build

* ci: apply automated fixes and generate docs

* chore: infer from SchemaV1

* ci: apply automated fixes and generate docs

* chore: fix zod form adapter test

* fix: legacy validators now work as intended for fields and onChange only

* Revert "fix: legacy validators now work as intended for fields and onChange only"

This reverts commit 266ea98.

* chore: wip migrate field away from return type to validator fn inferrencing

* chore: wip handle updating meta props

* chore: noinfer nothing

* chore: other cleanup on the API idea

* chore: revert adapter typing fix attempts

* chore: delete all form adapter codebases

* chore: remove now deleted packages from package.json

* chore: remove Yup examples

Yup does not yet support Standard Schema, so we're dropping support until it does

* feat: make standard schema return Issue objects, not a single string, add `disableErrorFlat` option, remove validators props

* chore: fix adapters types

* chore: remove old Zod and Valibot examples

Now we simply support standard schema

* chore: fix CI

* ci: apply automated fixes and generate docs

* chore: wip fixing issues with return types

* chore: WIP refactor return types

TODO: Refactor form adapters

* chore: add failing tests for `fields` type errors

* chore: fix edgecase with "fields" return type

* chore: fix standard schema typing edgecase

* ci: apply automated fixes and generate docs

* chore: fix Angular adapter's types

* ci: apply automated fixes and generate docs

* chore: fix Lit adapter

* ci: apply automated fixes and generate docs

* chore: fix react form types, add Standard Schema support to SSR adapters

* chore: fix typing problems with errors array

* chore: fix TS types for solid

* chore: fix Vue TS types

* chore: fix CI

* ci: apply automated fixes and generate docs

* docs: remove validatorAdapter mentions

* ci: apply automated fixes and generate docs

* docs: improve TanStack Start docs

* chore: fix build artifacts

* ci: apply automated fixes and generate docs

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
crutchcorn and autofix-ci[bot] authored Feb 21, 2025
1 parent c008984 commit d8ed149
Show file tree
Hide file tree
Showing 332 changed files with 11,217 additions and 10,814 deletions.
4 changes: 2 additions & 2 deletions docs/framework/angular/guides/basic-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Example:
`,
})
export class AppComponent {
firstNameValidator: FieldValidateFn<any, any, any, any, string> = ({
firstNameValidator: FieldValidateFn<any, any, string, any> = ({
value,
}) =>
!value
Expand All @@ -97,7 +97,7 @@ export class AppComponent {
? 'First name must be at least 3 characters'
: undefined
firstNameAsyncValidator: FieldValidateAsyncFn<any, any, any, any, string> =
firstNameAsyncValidator: FieldValidateAsyncFn<any, string, any> =
async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return value.includes('error') && 'No "error" allowed in first name'
Expand Down
87 changes: 39 additions & 48 deletions docs/framework/angular/guides/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,42 @@ export class AppComponent {
}
```

It's worth mentioning that our `errors` array and the `errorMap` matches the types returned by the validators. This means that:

```angular-ts
@Component({
selector: 'app-root',
standalone: true,
imports: [TanStackField],
template: `
<ng-container
[tanstackField]="form"
name="age"
[validators]="{
onChange: ageValidator
}"
#age="field"
>
<!-- ... -->
<!-- errorMap.onChange is type `{isOldEnough: false} | undefined` -->
<!-- meta.errors is type `Array<{isOldEnough: false} | undefined>` -->
@if (!age.api.state.meta.errorMap['onChange']?.isOldEnough) {
<em role="alert">The user is not old enough</em>
}
</ng-container>
`,
})
export class AppComponent {
ageValidator: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined
// ...
}
```




## Validation at field level vs at form level

As shown above, each `[tanstackField]` accepts its own validation rules via the `onChange`, `onBlur` etc... callbacks. It is also possible to define validation rules at the form level (as opposed to field by field) by passing similar callbacks to the `injectForm()` function.
Expand Down Expand Up @@ -288,7 +324,7 @@ To do this, we have dedicated `onChangeAsync`, `onBlurAsync`, and other methods
`,
})
export class AppComponent {
ageValidator: FieldValidateAsyncFn<any, any, any, any, number> = async ({
ageValidator: FieldValidateAsyncFn<any, string, number> = async ({
value,
}) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
Expand Down Expand Up @@ -332,7 +368,7 @@ export class AppComponent {
ensureAge13: FieldValidateFn<any, any, any, any, number> = ({ value }) =>
value < 13 ? 'You must be at least 13' : undefined
ensureOlderAge: FieldValidateAsyncFn<any, any, any, any, number> = async ({
ensureOlderAge: FieldValidateAsyncFn<any, string, number> = async ({
value,
}) => {
const currentAge = await fetchCurrentAgeOnProfile()
Expand Down Expand Up @@ -418,7 +454,7 @@ import { z } from 'zod'
`,
})
export class AppComponent {
form = injectForm({
form = injectForm({
// ...
})
Expand Down Expand Up @@ -466,51 +502,6 @@ export class AppComponent {
}
```

### Other Schema Libraries

We also support [Yup](https://github.com/jquense/yup) through an official adapter:

```bash
$ npm install @tanstack/yup-form-adapter yup
```

Once done, we can add the adapter to the `validator` property on the form or field:

```angular-ts
import { yupValidator } from '@tanstack/yup-form-adapter'
import * as yup from 'yup'
@Component({
selector: 'app-root',
standalone: true,
imports: [TanStackField],
template: `
<ng-container
[tanstackField]="form"
name="age"
[validators]="{
onChange: yup.number().moreThan(13, 'You must be 13 to make an account'),
}"
#age="field"
>
<!-- ... -->
</ng-container>
`,
})
export class AppComponent {
form = injectForm({
// Either add the validator here or on `Field`
validatorAdapter: yupValidator(),
// ...
})
yup = yup
// ...
}
```


## Preventing invalid forms from being submitted

The `onChange`, `onBlur` etc... callbacks are also run when the form is submitted and the submission is blocked if the form is invalid.
Expand Down
116 changes: 70 additions & 46 deletions docs/framework/angular/reference/classes/tanstackfield.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,76 @@ id: TanStackField
title: TanStackField
---

# Class: TanStackField\<TParentData, TName, TFieldValidator, TFormValidator, TData\>
# Class: TanStackField\<TParentData, TName, TData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TFormOnMount, TFormOnChange, TFormOnChangeAsync, TFormOnBlur, TFormOnBlurAsync, TFormOnSubmit, TFormOnSubmitAsync, TFormOnServer\>

Defined in: [tanstack-field.directive.ts:25](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L25)
Defined in: [tanstack-field.directive.ts:32](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L32)

## Type Parameters

**TParentData**

**TName** *extends* `DeepKeys`\<`TParentData`\>

**TFieldValidator** *extends*
\| `Validator`\<`DeepValue`\<`TParentData`, `TName`\>, `unknown`\>
\| `undefined` = `undefined`
**TData** *extends* `DeepValue`\<`TParentData`, `TName`\>

**TFormValidator** *extends* `Validator`\<`TParentData`, `unknown`\> \| `undefined` = `undefined`
**TOnMount** *extends* `undefined` \| `FieldValidateOrFn`\<`TParentData`, `TName`, `TData`\>

**TData** *extends* `DeepValue`\<`TParentData`, `TName`\> = `DeepValue`\<`TParentData`, `TName`\>
**TOnChange** *extends* `undefined` \| `FieldValidateOrFn`\<`TParentData`, `TName`, `TData`\>

**TOnChangeAsync** *extends* `undefined` \| `FieldAsyncValidateOrFn`\<`TParentData`, `TName`, `TData`\>

**TOnBlur** *extends* `undefined` \| `FieldValidateOrFn`\<`TParentData`, `TName`, `TData`\>

**TOnBlurAsync** *extends* `undefined` \| `FieldAsyncValidateOrFn`\<`TParentData`, `TName`, `TData`\>

**TOnSubmit** *extends* `undefined` \| `FieldValidateOrFn`\<`TParentData`, `TName`, `TData`\>

**TOnSubmitAsync** *extends* `undefined` \| `FieldAsyncValidateOrFn`\<`TParentData`, `TName`, `TData`\>

**TFormOnMount** *extends* `undefined` \| `FormValidateOrFn`\<`TParentData`\>

**TFormOnChange** *extends* `undefined` \| `FormValidateOrFn`\<`TParentData`\>

**TFormOnChangeAsync** *extends* `undefined` \| `FormAsyncValidateOrFn`\<`TParentData`\>

**TFormOnBlur** *extends* `undefined` \| `FormValidateOrFn`\<`TParentData`\>

**TFormOnBlurAsync** *extends* `undefined` \| `FormAsyncValidateOrFn`\<`TParentData`\>

**TFormOnSubmit** *extends* `undefined` \| `FormValidateOrFn`\<`TParentData`\>

**TFormOnSubmitAsync** *extends* `undefined` \| `FormAsyncValidateOrFn`\<`TParentData`\>

**TFormOnServer** *extends* `undefined` \| `FormAsyncValidateOrFn`\<`TParentData`\>

## Implements

- `OnInit`
- `OnChanges`
- `OnDestroy`
- `FieldOptions`\<`TParentData`, `TName`, `TFieldValidator`, `TFormValidator`, `TData`\>
- `FieldOptions`\<`TParentData`, `TName`, `TData`, `TOnMount`, `TOnChange`, `TOnChangeAsync`, `TOnBlur`, `TOnBlurAsync`, `TOnSubmit`, `TOnSubmitAsync`\>

## Constructors

### new TanStackField()

```ts
new TanStackField<TParentData, TName, TFieldValidator, TFormValidator, TData>(): TanStackField<TParentData, TName, TFieldValidator, TFormValidator, TData>
new TanStackField<TParentData, TName, TData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TFormOnMount, TFormOnChange, TFormOnChangeAsync, TFormOnBlur, TFormOnBlurAsync, TFormOnSubmit, TFormOnSubmitAsync, TFormOnServer>(): TanStackField<TParentData, TName, TData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TFormOnMount, TFormOnChange, TFormOnChangeAsync, TFormOnBlur, TFormOnBlurAsync, TFormOnSubmit, TFormOnSubmitAsync, TFormOnServer>
```

#### Returns

[`TanStackField`](tanstackfield.md)\<`TParentData`, `TName`, `TFieldValidator`, `TFormValidator`, `TData`\>
[`TanStackField`](tanstackfield.md)\<`TParentData`, `TName`, `TData`, `TOnMount`, `TOnChange`, `TOnChangeAsync`, `TOnBlur`, `TOnBlurAsync`, `TOnSubmit`, `TOnSubmitAsync`, `TFormOnMount`, `TFormOnChange`, `TFormOnChangeAsync`, `TFormOnBlur`, `TFormOnBlurAsync`, `TFormOnSubmit`, `TFormOnSubmitAsync`, `TFormOnServer`\>

## Properties

### api

```ts
api: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>;
api: FieldApi<TParentData, TName, TData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TFormOnMount, TFormOnChange, TFormOnChangeAsync, TFormOnBlur, TFormOnBlurAsync, TFormOnSubmit, TFormOnSubmitAsync, TFormOnServer>;
```

Defined in: [tanstack-field.directive.ts:62](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L62)
Defined in: [tanstack-field.directive.ts:131](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L131)

***

Expand All @@ -58,7 +82,7 @@ Defined in: [tanstack-field.directive.ts:62](https://github.com/TanStack/form/bl
optional asyncAlways: boolean;
```

Defined in: [tanstack-field.directive.ts:48](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L48)
Defined in: [tanstack-field.directive.ts:81](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L81)

If `true`, always run async validation, even if there are errors emitted during synchronous validation.

Expand All @@ -76,7 +100,7 @@ FieldOptions.asyncAlways
optional asyncDebounceMs: number;
```

Defined in: [tanstack-field.directive.ts:47](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L47)
Defined in: [tanstack-field.directive.ts:80](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L80)

The default time to debounce async validation if there is not a more specific debounce time passed.

Expand All @@ -91,10 +115,10 @@ FieldOptions.asyncDebounceMs
### defaultMeta?

```ts
optional defaultMeta: Partial<FieldMeta>;
optional defaultMeta: Partial<FieldMeta<TParentData, TName, TData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TFormOnMount, TFormOnChange, TFormOnChangeAsync, TFormOnBlur, TFormOnBlurAsync, TFormOnSubmit, TFormOnSubmitAsync>>;
```

Defined in: [tanstack-field.directive.ts:60](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L60)
Defined in: [tanstack-field.directive.ts:108](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L108)

An optional object with default metadata for the field.

Expand All @@ -112,7 +136,7 @@ FieldOptions.defaultMeta
optional defaultValue: NoInfer<TData>;
```

Defined in: [tanstack-field.directive.ts:46](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L46)
Defined in: [tanstack-field.directive.ts:79](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L79)

An optional default value for the field.

Expand All @@ -124,13 +148,31 @@ FieldOptions.defaultValue

***

### disableErrorFlat?

```ts
optional disableErrorFlat: boolean;
```

Defined in: [tanstack-field.directive.ts:129](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L129)

Disable the `flat(1)` operation on `field.errors`. This is useful if you want to keep the error structure as is. Not suggested for most use-cases.

#### Implementation of

```ts
FieldOptions.disableErrorFlat
```

***

### listeners?

```ts
optional listeners: NoInfer<FieldListeners<TParentData, TName, TFieldValidator, TFormValidator, TData>>;
optional listeners: NoInfer<FieldListeners<TParentData, TName, TData>>;
```

Defined in: [tanstack-field.directive.ts:57](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L57)
Defined in: [tanstack-field.directive.ts:107](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L107)

A list of listeners which attach to the corresponding events

Expand All @@ -148,7 +190,7 @@ FieldOptions.listeners
name: TName;
```

Defined in: [tanstack-field.directive.ts:42](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L42)
Defined in: [tanstack-field.directive.ts:75](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L75)

The field name. The type will be `DeepKeys<TParentData>` to ensure your name is a deep key of the parent dataset.

Expand All @@ -163,10 +205,10 @@ FieldOptions.name
### tanstackField

```ts
tanstackField: FormApi<TParentData, TFormValidator>;
tanstackField: FormApi<TParentData, TFormOnMount, TFormOnChange, TFormOnChangeAsync, TFormOnBlur, TFormOnBlurAsync, TFormOnSubmit, TFormOnSubmitAsync, TFormOnServer>;
```

Defined in: [tanstack-field.directive.ts:50](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L50)
Defined in: [tanstack-field.directive.ts:82](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L82)

***

Expand All @@ -176,39 +218,21 @@ Defined in: [tanstack-field.directive.ts:50](https://github.com/TanStack/form/bl
optional unmount: () => void;
```

Defined in: [tanstack-field.directive.ts:78](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L78)
Defined in: [tanstack-field.directive.ts:185](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L185)

#### Returns

`void`

***

### validatorAdapter?

```ts
optional validatorAdapter: TFieldValidator;
```

Defined in: [tanstack-field.directive.ts:49](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L49)

A validator provided by an extension, like `yupValidator` from `@tanstack/yup-form-adapter`

#### Implementation of

```ts
FieldOptions.validatorAdapter
```

***

### validators?

```ts
optional validators: NoInfer<FieldValidators<TParentData, TName, TFieldValidator, TFormValidator, TData>>;
optional validators: NoInfer<FieldValidators<TParentData, TName, TData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync>>;
```

Defined in: [tanstack-field.directive.ts:54](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L54)
Defined in: [tanstack-field.directive.ts:93](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L93)

A list of validators to pass to the field

Expand All @@ -226,7 +250,7 @@ FieldOptions.validators
ngOnChanges(): void
```

Defined in: [tanstack-field.directive.ts:90](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L90)
Defined in: [tanstack-field.directive.ts:197](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L197)

A callback method that is invoked immediately after the
default change detector has checked data-bound properties
Expand All @@ -251,7 +275,7 @@ OnChanges.ngOnChanges
ngOnDestroy(): void
```

Defined in: [tanstack-field.directive.ts:86](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L86)
Defined in: [tanstack-field.directive.ts:193](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L193)

A callback method that performs custom clean-up, invoked immediately
before a directive, pipe, or service instance is destroyed.
Expand All @@ -274,7 +298,7 @@ OnDestroy.ngOnDestroy
ngOnInit(): void
```

Defined in: [tanstack-field.directive.ts:80](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L80)
Defined in: [tanstack-field.directive.ts:187](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L187)

A callback method that is invoked immediately after the
default change detector has checked the directive's
Expand Down
Loading

0 comments on commit d8ed149

Please sign in to comment.