Skip to content

Using our HookFormPF* components

Mike Turley edited this page Oct 28, 2022 · 17 revisions

We have a set of components in tackle2-ui that abstract away some of the boilerplate required to properly render field labels and validation state when using react-hook-form with PatternFly. Here are some basic steps you'll need to follow when implementing a new form using these components.

For an example you can look at the proxy-form (in the UI under admin view -> Proxy) or the identity-form (in the UI under admin view -> Credentials -> Create new).

  • Create a TypeScript interface for all your form value keys/types (example)

  • Define a validation schema for your values using yup (example). The schema should be annotated with the type yup.SchemaOf<YourValuesInterface>.

    • See yup v0.32.11 docs here. Note that their main branch README is the unreleased v1.0.0-beta we aren't using yet.
    • For TypeScript to be happy, you may have to chain .defined() on some field schema types so it doesn't yell at you for e.g. Type 'string | undefined' is not assignable to type 'string'.
    • You probably want to have your schema returned by a function beginning with the word use, so it can fit with the React Hooks conventions and call useTranslation() to get you the t() function for translated validation error messages. Or if the form is small enough you can just define the schema inline in the yupResolver() call below.
  • Call useForm (see react-hook-form docs) and pass it your interface as a type param (example). Pass it an object with defaultValues, resolver: yupResolver(yourSchemaHere), and mode: "onChange" (this will make it revalidate when any field changes, as opposed to requiring manual imperative validation. we need it for the way we render errors).

    • useForm returns an object with a bunch of stuff. We usually destructure it in-place like you see here. Important things you'll need to pull out include control, handleSubmit, probably formState if you need to block some buttons based on validation, getValues and setValue if you need to get/set anything manually (might not be necessary), probably watch.

    • Below there you see const values = getValues();... I think we actually want const values = watch(); but as long as you're using at least one field via our controller components below I think it auto-watches the form values anyway. (react-hook-form has some perf optimizations we are disabling here because they don't fit great with PatternFly. we just want a full re-render when any value changes).

  • Write an onSubmit function of type SubmitHandler<YourValuesInterface> (example). Wrap your form fields in a PF <Form> component with onSubmit={handleSubmit(onSubmit)}(example) where handleSubmit came from your useForm call. That's where you'll eventually want to do the submit logic, probably using mutations from react-query.

    • Render a <Button type="submit"> at the bottom of your form (example), with isDisabled tied to whatever you need for blocking the form (including !formState.isValid from useForm). You don't need an onClick here, this button will trigger the handleSubmit defined above.
  • Now you can use our new components for the fields themselves. They will take care of rendering the PF FormGroups and properly styled validation errors. Pass them the control prop from your useForm call and a name string prop matching the field name key from your form values object. TS is smart enough to infer the right field value type from those 2 props.

    • If you're rendering a basic text input, you can use HookFormPFTextInput (source, example). It extends the props of PatternFly's TextInput, so you can pass whatever extra stuff you need directly into it.
    • Same for a multi-line textarea, you can use HookFormPFTextArea(source, example) which extends the props of PF TextArea.
    • For any other type of field that requires a PF FormGroup (label, error messages under the field) you can use the HookFormPFGroupController that is used internally by those 2 components, and pass it your own renderInput function. (source, example). For select dropdowns we have a simplified abstraction called SimpleSelect (needs some work tbh).
    • These all use the Controller pattern from react-hook-form (docs here and here). You generally don't want to use the {...register('fieldName')} approach that is all over their docs, it is for uncontrolled inputs (we need controlled inputs to render errors on change).
    • All of the above components include a formGroupProps prop in case you need to override any of the props for PF's FormGroup that aren't taken care of for you.
    • If you don't need a FormGroup around your field (no external label or errors), you can just render a <Controller> for it yourself. That's what we do for Switch fields (because switch has a built in right-aligned label). (example)
Clone this wiki locally