Allow to avoid wrapping every fieldComponent with AppField
I'd like to do this:
<Form.TextInput fieldName={"name"} labelText="Name" />
instead of
<form.AppField name={"name"}>
{(field) => <field.TextInput labelText="Name" />}
</form.AppField>
I was able to implement it with proper typing but avoiding fieldComponents.
export const useAppForm = <
TFormData,
TOnMount extends undefined | FormValidateOrFn<TFormData>,
TOnChange extends undefined | FormValidateOrFn<TFormData>,
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnBlur extends undefined | FormValidateOrFn<TFormData>,
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
TSubmitMeta = never,
>(
options: FormOptions<
TFormData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnServer,
TSubmitMeta
>
) => {
const form = useAppFormTanStack(options);
const id = useId();
useEffect(() => {
form.baseStore.setState((s) => ({ ...s, id }));
}, []);
const FieldWrapper =
<TProps extends {}>(Component: React.ComponentType<TProps>) =>
({
fieldName,
mode,
...rest
}: TProps & {
fieldName: DeepKeys<TFormData>;
mode?: "array";
}) => {
return (
<form.AppField name={fieldName} mode={mode}>
{() => <Component {...(rest as any)} />}
</form.AppField>
);
};
const Fields = {
TextInput: useMemo(() => FieldWrapper(TextInputField), []),
SelectInput: useMemo(() => FieldWrapper(SelectField), []),
MultiSelect: useMemo(() => FieldWrapper(MultiSelectField), []),
CheckBox: useMemo(() => FieldWrapper(CheckBoxField), []),
RadioButtonGroup: useMemo(() => FieldWrapper(RadioButtonGroupField), []),
NumberInput: useMemo(() => FieldWrapper(NumberInputField), []),
TextArea: useMemo(() => FieldWrapper(TextAreaField), []),
DatePicker: useMemo(() => FieldWrapper(DatePickerField), [])
};
return { form, Fields };
};
Can we support it natively with fieldComponents?
usage:
<Fields.TextInput fieldName={"name"} labelText="Name" />
same question , @Balastrong can you give some advice
Would also like to see this
The reasoning for AppField is that you separate the field properties from the component properties.
See the example you provided:
<form.AppField name="name">
{(field) => <field.TextInput labelText="Name" />}
</form.AppField>
This allows the data to be clearly categorized as form-related or as field-related
AppField:
- The path to the field value (
name) - Field-level validation (
validators) - Listeners (
listeners) - The type of field (
mode)
field.TextInput
- The value of the field
- What to do on change
- What to do on blur
- Label text
- Any additional properties related to styling or implementation
AppField allows you to reduce the boilerplate of things like value, onBlur & onChange. I would not recommend merging the field since there's currently a nice separation of concerns that you would lose with your suggestion.
@LeCarbonator
But we reduce it inside Fields.TextInput. All these fields inside Fields are already wrappers around UI libraries like MUI which connects AppField functionality with UI component. For me Fields.TextInput is also a good indicator that this is a form field. Nothing actually stops us from putting it inside form and use in the same manner with oneliner:
<form.TextInput fieldName={"name"} labelText="Name" />
field.TextInput already contains all the logic you mentioned earlier, it's already mixing form methods with ui methods. The only difference is that it's only retrievable by first doing <form.AppField name="name"> which doesn't make much sense and has really no advantage at all.
But we reduce it inside Fields.TextInput. All these fields inside Fields are already wrappers around UI libraries like MUI which connects AppField functionality with UI component. For me Fields.TextInput is also a good indicator that this is a form field. Nothing actually stops us from putting it inside form and use in the same manner with oneliner:
<form.TextInput fieldName={"name"} labelText="Name" />
As said, this is about separation of concerns. You have Logic (validation, form value path, listeners) and you have Display (what to show with which data). AppField concerns itself with the Logic and TextInput concerns itself with the Display.
You're proposing merging those two together, but the two elements inherently don't deal with the same thing at all.
field.TextInputalready contains all the logic you mentioned earlier, it's already mixing form methods with ui methods. The only difference is that it's only retrievable by first doing <form.AppField name="name"> which doesn't make much sense and has really no advantage at all.
You phrased it correctly, it mixes it. You reduced three lines into one, but you also mixed in two different categories into one element that now deals with both. The advantage is that you can tell that TextInput will not be related to anything validator / listener related as it's not its job.