Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form binding support for React Vaadin components #587

Open
platosha opened this issue Oct 10, 2022 · 9 comments
Open

Form binding support for React Vaadin components #587

platosha opened this issue Oct 10, 2022 · 9 comments
Labels
Epic hilla Issues related to Hilla react

Comments

@platosha
Copy link
Contributor

No description provided.

@platosha platosha added hilla Issues related to Hilla react labels Oct 10, 2022
@cromoteca
Copy link
Contributor

This is what I can see by building a simple form using Hilla, React, Formik, and Yup. Consider an endpoint like this one:

@Endpoint
@AnonymousAllowed
public class HelloFormEndpoint {

  @Nonnull
  public String validate(@Nonnull RegistrationInfo info) {
    return "Registration accepted";
  }

  public static record RegistrationInfo(
      @NotBlank String name,
      @NotBlank @Email String email,
      @Pattern(regexp = "^[0-9]+$") String phone,
      @Size(min = 2, max = 2) String country,
      @AssertTrue boolean conditions) {}
}

It can be used in this form:

image

<Formik
  initialValues={{ ...initialValues }}
  validationSchema={yupSchema}
  onSubmit={async values => {
    const response = await HelloFormEndpoint.validate(values);
    Notification.show(response, { theme: 'success' });
  }}
>
  {({ submitForm, isValid, dirty }) => (
    <Form>
      <VerticalLayout theme="spacing padding">
        <Field name="name">
          {({ field, meta }: any) => (
            <TextField {...field} placeholder="Name"
              invalid={meta.error && meta.touched} errorMessage={meta.error} />
          )}
        </Field>

        <Field name="email">
          {({ field, meta }: any) => (
            <TextField {...field} placeholder="Email"
              invalid={meta.error && meta.touched} errorMessage={meta.error} />
          )}
        </Field>

        <Field name="phone">
          {({ field, meta }: any) => (
            <TextField {...field} placeholder="Phone"
              invalid={meta.error && meta.touched} errorMessage={meta.error} />
          )}
        </Field>

        <Field name="country">
          {({ field, meta }: any) => (
            <Select {...field} placeholder="Country" items={countries} name={field.name}
              invalid={meta.error && meta.touched} errorMessage={meta.error} />
          )}
        </Field>

        <Field name="conditions">
          {({ field, meta }: any) =>
            <Checkbox {...field} label="I accept terms and conditions"
              invalid={meta.error && meta.touched} errorMessage={meta.error}
              onCheckedChanged={field.onChange} />
          }
        </Field>

        <Button theme="primary" onClick={submitForm} disabled={!(dirty && isValid)}>Submit</Button>
      </VerticalLayout>
    </Form>
  )}
</Formik>

Both initialValues and yupSchema could be generated, just like it happens in Lit (see #585):

const initialValues: RegistrationInfo = {
  name: '',
  email: '',
  phone: '',
  country: '',
  conditions: false,
};

const yupSchema: ObjectSchema<RegistrationInfo> = object({
  name: string().required(),
  email: string().email().required(),
  phone: string().matches(/^[0-9]+$/).required(),
  country: string().min(2).max(2).required(),
  conditions: boolean().oneOf([true]).required(),
});

Concerning fields, there's a clear pattern that could be formalized and implemented to replace them with something like <FormTextField/>, <FormSelect/>, <FormCheckbox/>, and so on. If they were one-liners, the form would be really compact:

<Formik
  initialValues={{ ...initialValues }}
  validationSchema={yupSchema}
  onSubmit={async values => {
    const response = await HelloFormEndpoint.validate(values);
    Notification.show(response, { theme: 'success' });
  }}
>
  {({ submitForm, isValid, dirty }) => (
    <Form>
      <VerticalLayout theme="spacing padding">
        <FormTextField name="name" placeholder="Name" />
        <FormTextField name="email" placeholder="Email" />
        <FormTextField name="phone" placeholder="Phone" />
        <FormSelect name="country" placeholder="Country" items={countries} />
        <FormCheckbox name="conditions" label="I accept terms and conditions" />

        <Button theme="primary" onClick={submitForm} disabled={!(dirty && isValid)}>Submit</Button>
      </VerticalLayout>
    </Form>
  )}
</Formik>

@platosha
Copy link
Contributor Author

Let's consider a generic FormField instead of wrapping every component in existence as FormTextField and so on:

   <Form>
      <FormLayout>
        <FormField component={TextField} name="name" label="Name" />
        <FormField component={EmailField}  name="email" label="Email" />
  
        <Button theme="primary" onClick={submitForm} disabled={!(dirty && isValid)}>Submit</Button>
      </FormLayout>
    </Form>

This would then support both HTML and Vaadin React components, and even custom third-party components, for as long as they are based on Vaadin components or follow Vaadin conventions to some extent.

@cromoteca
Copy link
Contributor

cromoteca commented Apr 14, 2023

That would be great, but how do you handle special needs like the onCheckedChanged={field.onChange} I had to add to the Checkbox?

I also don't see the difference between our FormField and Formik's Field: you can do <Field as={TextField}.../>

@platosha
Copy link
Contributor Author

I assume we can rely on events that work with all components, such as HTML standard onInput / onChange.

The Formik's Field does not have built-in support for invalid and errorMessage, this is something we could improve with our directive.

@gitgmihd
Copy link

Hello everyone!

I'm interested in the discussion on React Form support in Hilla and wanted to share my thoughts on the use of Formik for field validation. While Formik has been a popular choice for managing form state and validation in React, I noticed that its development activity seems to be slowing down - the last release (2.2.9) was back in June 2021.

In light of this, I'd like to suggest considering React Hook Form instead, as it's actively developed and provides an easy-to-use API for building forms in React. I found some helpful information on this topic in the 'Is Formik dead?' issue on GitHub (jaredpalmer/formik#3526).

What are your thoughts on this? Have you considered React Hook Form as an alternative to Formik for hilla?
Thank you for your consideration.

@cromoteca
Copy link
Contributor

Hello, yes, React Hook Form is definitely in the list of candidates. The link you cited confirms that it should get more attention than Formik.

@marcushellberg
Copy link
Member

Should we change the title of this ticket until we decide which form library we plan to support?

@platosha platosha changed the title Formik support for React Vaadin components Form binding support for React Vaadin components Apr 24, 2023
@platosha
Copy link
Contributor Author

platosha commented May 5, 2023

Now that we have realised that Formik is not a sustainable choice, we have to pick some other library. While React Hook Form is a good candidate, the alternative that we are also considering right now is adapting the existing Hilla+Lit form binder to React.

Right now it seems that the complexity in the form binding feature is not in the management of the from state, lifecycle, and client-side validation (all libraries are reasonably good at this), but rather in integration with Vaadin components and Hilla endpoints, both of which use a number of custom conventions / APIs, that are not generally supported by third-party form libraries out-of-the-box. Reusing Hilla+Lit form binder is a quick way to address all these integration challenges (both this issue and the related validation one #585), with some additional benefits in the resulting solution.

I did some prototyping for the idea of using Hilla form binder with React and hooks. Here is the API I came up with in the prototype:

export default function FormView() {
  const { model, submit } = useBinder(EntityModel, {onSubmit: FormEndpoint.sendEntity});

  return (
    <>
      <section className="flex p-m gap-m items-baseline">
        <TextField label="Name" {...field(model.name)}></TextField>
        <ComboBox label="Choose" {...field(model.choice)} items={comboBoxItems}></ComboBox>
        <NumberField label="Number" {...field(model.number)}></NumberField>
        <DatePicker label="Date" {...field(model.date)}></DatePicker>
        <Button onClick={submit}>submit</Button>
      </section>
    </>
  );
}

The result is quite similar to React Hook Form. This is not a big surprise to me, as we took some inspiration from React hooks in the original Hilla+Lit form binder itself.

Please share your feedback about this idea and the API.

@platosha
Copy link
Contributor Author

Let us use this issue as an epic for the other Hilla React binder implementation issues.

@platosha platosha added the Epic label Aug 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Epic hilla Issues related to Hilla react
Projects
Archived in project
Development

No branches or pull requests

5 participants