Skip to content

Commit

Permalink
feat(core): add field listeners (#1032)
Browse files Browse the repository at this point in the history
* feat: add field listeners

* feat: add onBlur listener

* feat: add onSubmit listener

* ci: apply automated fixes and generate docs

* fix: stop onChange triggering onMount and add tests to assert this

* feat: add listeners react documentation

* fix: pull request comments

* ci: apply automated fixes and generate docs

* fix: pull request comments

* feat: Vue docs

* feat: Angular docs

* feat: expose listeners on anguar wrapper and update docs

* docs: fix react

* docs: fix vue

* ci: apply automated fixes and generate docs

---------

Co-authored-by: ha1fstack <[email protected]>
Co-authored-by: Harry Whorlow <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Leonardo Montini <[email protected]>
  • Loading branch information
5 people authored Nov 25, 2024
1 parent 1db18b2 commit 6968cfd
Show file tree
Hide file tree
Showing 22 changed files with 749 additions and 79 deletions.
4 changes: 4 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@
"label": "Linked Fields",
"to": "framework/react/guides/linked-fields"
},
{
"label": "Listeners",
"to": "framework/react/guides/listeners"
},
{
"label": "UI Libraries",
"to": "framework/react/guides/ui-libraries"
Expand Down
35 changes: 35 additions & 0 deletions docs/framework/angular/guides/basic-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,41 @@ class AppComponent {
}
```

## Listeners

`@tanstack/angular-form` allows you to react to specific triggers and "listen" to them to dispatch side effects.

Example:

```angular-ts
@Component({
selector: 'app-root',
standalone: true,
imports: [TanStackField],
template: `
<ng-container
[tanstackField]="form"
name="country"
[listeners]="{
onChange: onCountryChange
}"
#country="field"
></ng-container>
`,
})
...
onCountryChange: FieldListenerFn<any, any, any, any, string> = ({
value,
}) => {
console.log(`Country changed to: ${value}, resetting province`)
this.form.setFieldValue('province', '')
}
```

More information can be found at [Listeners](./listeners.md)

## Array Fields

Array fields allow you to manage a list of values within a form, such as a list of hobbies. You can create an array field using the `tanstackField` directive.
Expand Down
61 changes: 61 additions & 0 deletions docs/framework/angular/guides/listeners.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
id: listeners
title: Side effects for event triggers
---

For situations where you want to "affect" or "react" to triggers, there's the listener API. For example, if you, as the developer, want to reset a form field as a result of another field changing, you would use the listener API.

Imagine the following user flow:

- User selects a country from a drop-down.
- User then selects a province from another drop-down.
- User changes the selected country to a different one.

In this example, when the user changes the country, the selected province needs to be reset as it's no longer valid. With the listener API, we can subscribe to the onChange event and dispatch a reset to the field "province" when the listener is fired.

Events that can be "listened" to are:

- onChange
- onBlur
- onMount
- onSubmit

```angular-ts
@Component({
selector: 'app-root',
standalone: true,
imports: [TanStackField],
template: `
<ng-container
[tanstackField]="form"
name="country"
[listeners]="{
onChange: onCountryChange
}"
#country="field"
></ng-container>
<ng-container
[tanstackField]="form"
name="province"
#province="field"
></ng-container>
`,
})
export class AppComponent {
form = injectForm({
defaultValues: {
country: '',
province: '',
},
})
onCountryChange: FieldListenerFn<any, any, any, any, string> = ({
value,
}) => {
console.log(`Country changed to: ${value}, resetting province`)
this.form.setFieldValue('province', '')
}
}
```
44 changes: 31 additions & 13 deletions docs/framework/angular/reference/classes/tanstackfield.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ api: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>;

#### Defined in

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

***

Expand All @@ -64,7 +64,7 @@ If `true`, always run async validation, even if there are errors emitted during

#### Defined in

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

***

Expand All @@ -82,7 +82,7 @@ The default time to debounce async validation if there is not a more specific de

#### Defined in

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

***

Expand All @@ -100,7 +100,7 @@ An optional object with default metadata for the field.

#### Defined in

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

***

Expand All @@ -118,7 +118,25 @@ An optional default value for the field.

#### Defined in

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

***

### listeners?

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

A list of listeners which attach to the corresponding events

#### Implementation of

`FieldOptions.listeners`

#### Defined in

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

***

Expand All @@ -136,7 +154,7 @@ The field name. The type will be `DeepKeys<TParentData>` to ensure your name is

#### Defined in

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

***

Expand All @@ -148,7 +166,7 @@ tanstackField: FormApi<TParentData, TFormValidator>;

#### Defined in

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

***

Expand All @@ -164,7 +182,7 @@ optional unmount: () => void;

#### Defined in

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

***

Expand All @@ -182,7 +200,7 @@ A validator provided by an extension, like `yupValidator` from `@tanstack/yup-fo

#### Defined in

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

***

Expand All @@ -200,7 +218,7 @@ A list of validators to pass to the field

#### Defined in

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

## Methods

Expand All @@ -225,7 +243,7 @@ children are checked.

#### Defined in

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

***

Expand All @@ -248,7 +266,7 @@ before a directive, pipe, or service instance is destroyed.

#### Defined in

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

***

Expand All @@ -274,4 +292,4 @@ It is invoked only once when the directive is instantiated.

#### Defined in

[tanstack-field.directive.ts:75](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L75)
[tanstack-field.directive.ts:80](https://github.com/TanStack/form/blob/main/packages/angular-form/src/tanstack-field.directive.ts#L80)
20 changes: 20 additions & 0 deletions docs/framework/react/guides/basic-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,26 @@ const firstName = form.useStore((state) => state.values.firstName)

Note: The usage of the `form.useField` hook to achieve reactivity is discouraged since it is designed to be used thoughtfully within the `form.Field` component. You might want to use `form.useStore` instead.

## Listeners

`@tanstack/react-form` allows you to react to specific triggers and "listen" to them to dispatch side effects.

Example:

```tsx
<form.Field
name="country"
listeners={{
onChange: ({ value }) => {
console.log(`Country changed to: ${value}, resetting province`)
form.setFieldValue('province', '')
}
}}
/>
```

More information can be found at [Listeners](./listeners.md)

## Array Fields

Array fields allow you to manage a list of values within a form, such as a list of hobbies. You can create an array field using the `form.Field` component with the `mode="array"` prop.
Expand Down
67 changes: 67 additions & 0 deletions docs/framework/react/guides/listeners.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
id: listeners
title: Side effects for event triggers
---

For situations where you want to "affect" or "react" to triggers, there's the listener API. For example, if you, as the developer, want to reset a form field as a result of another field changing, you would use the listener API.

Imagine the following user flow:

- User selects a country from a drop-down.
- User then selects a province from another drop-down.
- User changes the selected country to a different one.

In this example, when the user changes the country, the selected province needs to be reset as it's no longer valid. With the listener API, we can subscribe to the onChange event and dispatch a reset to the field "province" when the listener is fired.

Events that can be "listened" to are:

- onChange
- onBlur
- onMount
- onSubmit

```tsx
function App() {
const form = useForm({
defaultValues: {
country: '',
province: '',
},
// ...
})

return (
<div>
<form.Field name="country">
{(field) => (
<label>
<div>Country</div>
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
listeners={{
onChange: ({ value }) => {
console.log(`Country changed to: ${value}, resetting province`)
form.setFieldValue('province', '')
}
}}
/>
</label>
)}
</form.Field>

<form.Field name="province">
{(field) => (
<label>
<div>Province</div>
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
</label>
)}
</form.Field>
</div>
)
}
```
29 changes: 29 additions & 0 deletions docs/framework/vue/guides/basic-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,35 @@ const firstName = form.useStore((state) => state.values.firstName)
</template>
```

## Listeners

`@tanstack/vue-form` allows you to react to specific triggers and "listen" to them to dispatch side effects.

Example:

```vue
<template>
<form.Field
name="country"
:listeners="{
onChange: ({ value }) => {
console.log(`Country changed to: ${value}, resetting province`)
form.setFieldValue('province', '')
}
}"
>
<template v-slot="{ field }">
<input
:value="field.state.value"
@input="(e) => field.handleChange(e.target.value)"
/>
</template>
</form.Field>
</template>
```

More information can be found at [Listeners](./listeners.md)

Note: The usage of the `form.useField` method to achieve reactivity is discouraged since it is designed to be used thoughtfully within the `form.Field` component. You might want to use `form.useStore` instead.

## Array Fields
Expand Down
Loading

0 comments on commit 6968cfd

Please sign in to comment.