feat(core): add field listeners
from discussion #709
Background
Sometimes we may want to attach our own side effects to the field when some event happends. For example, resetting another field if some field value has changed.
Attaching these 'listeners' inside the validator does not work as intended because validator does not guarantee if it was triggered by the exact event. (eg. all validators run when form submits)
It's of course possible to implement but it's not very clean and the concerns become scattered.
Proposal
This PR adds listeners api to the field for easy handling of forementioned use cases.
onChange and onHandleChange are differentiated because onHandleChange captures the 'manual' changes to the field. (eg. user input) while onChange also captures programmatic changes. (eg. setValue)
Thoughts
I'm not certain about the whole idea or implementation, open to suggestions.
This feature will be useful +1. We are currently hacking around validators by it not practical
This feature will be useful +1. We are currently hacking around
validatorsby it not practical
@zaosoula Bit clumsy code, but in the meantime you can do this:
import { DeepKeys, DeepValue, FieldApi, functionalUpdate, Validator } from '@tanstack/form-core';
export function attachOnChangeToField<
TParentData,
TName extends DeepKeys<TParentData>,
TFieldValidator extends Validator<DeepValue<TParentData, TName>, unknown> | undefined = undefined,
TFormValidator extends Validator<TParentData, unknown> | undefined = undefined,
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>
>(
field: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>,
onChange: (props: {
value: DeepValue<TParentData, TName>;
fieldApi: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>;
}) => void
) {
const handleChange: typeof field.handleChange = updater => {
onChange({
value: functionalUpdate(updater, field.store.state.value),
fieldApi: field,
});
field.handleChange(updater);
};
return {
...field,
handleChange,
} as typeof field;
}
export function AttachOnchangeToField<
TParentData,
TName extends DeepKeys<TParentData>,
TFieldValidator extends Validator<DeepValue<TParentData, TName>, unknown> | undefined = undefined,
TFormValidator extends Validator<TParentData, unknown> | undefined = undefined,
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>
>(props: {
field: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>;
onChange: (props: {
value: DeepValue<TParentData, TName>;
fieldApi: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>;
}) => void;
children: (field: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>) => JSX.Element;
}) {
const { field, onChange } = props;
const newField = attachOnChangeToField(field, onChange);
return props.children(newField);
}
<form.Field name="name">
{field => (
<AttachOnchangeToField
field={field}
onChange={({ value, fieldApi }) => { ... }
>
{field => ...
@zaosoula this would be helpful to understand a practical use case :) Can you share yours?
@crutchcorn
![]()
Here we are using onChange to dynamically set a checkbox to true if the selected value match a list
![]()
Here we are using onChangeAsync to open a modal to create a new entry for a sub-relation
Each time we have to return null or an empty string as we do not want to show errors; using the validators property is also confusing for new developper reading our code as the functions above are clearly not validators
This alone makes a lot of sense to me. Let's move forward with the idea. Reviewing the code now
Any news on this feature ?
@ha1fstack @crutchcorn If this task is stale, I would like to pick it up as this functionality is something we would really like.
For us, the use case as an example, is a payment form where if you select credit_card as a payment method you have a form field called creditCardBrands, which needs to be cleared when you change the payment method back to anything other than credit_card.
Personally, this is something that is not an infrequent use case, and having an api that's simpler than a hacky workaround utilising the validator linked-fields, is something that I feel is needed.
Let me know if I can take it up 🤟
Completed in #1032