-
Notifications
You must be signed in to change notification settings - Fork 43
Using our HookFormPF* components
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 calluseTranslation()
to get you thet()
function for translated validation error messages. Or if the form is small enough you can just define the schema inline in theyupResolver()
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)
, andmode: "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 includecontrol
,handleSubmit
, probablyformState
if you need to block some buttons based on validation,getValues
andsetValue
if you need to get/set anything manually (might not be necessary), probablywatch
. -
Below there you see
const values = getValues();
... I think we actually wantconst 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 typeSubmitHandler<YourValuesInterface>
(example). Wrap your form fields in a PF<Form>
component withonSubmit={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), withisDisabled
tied to whatever you need for blocking the form (including!formState.isValid
from useForm). You don't need anonClick
here, this button will trigger thehandleSubmit
defined above.
- Render a
-
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 aname
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 ownrenderInput
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)
- If you're rendering a basic text input, you can use