Multiple subcomponents
When dealing with a big form, it is useful to group some fields and break them up into multiple subcomponents, then handle them with useFormikContext
#
1. Main form componentThese will render the main formik component, the form sections via subcomponents and usually also the submit button section.
components/payment-types/details/PaymentTypeForm.tsx
export interface IPaymentType { Name: string; Code: string;}
export const PaymentTypeForm = () => { const intl = useIntl(); const paymentType = useRecoilValue(getPaymentTypeState.response);
// Initial form values when loading the form const initialValues: IPaymentType = useMemo( () => ({ ID: paymentType?.ID, Name: paymentType?.Name, Code: paymentType?.Code, PaymentMethodID: paymentType?.PaymentMethodID, BackendRelationID: paymentType?.BackendRelationID, OrganizationUnitSetID: paymentType?.OrganizationUnitSetID, CashJournalMethod: paymentType?.CashJournalMethod, ReturnAction: paymentType?.ReturnAction, LedgerClassID: paymentType?.LedgerClassID, CaptureMoment: paymentType?.CaptureMoment, Category: paymentType?.Category, AutoFinalizeOnOrderPaid: paymentType?.AutoFinalizeOnOrderPaid, IsRoundingType: paymentType?.IsRoundingType, PrintOnDocuments: paymentType?.PrintOnDocuments, IsActive: paymentType?.IsActive, Options: { ...paymentType?.Options, }, }), [paymentType], );
// Callback to save the form values in the backend and a loading state which indicates that a save is in progress const { savePaymentType, isSaveLoading } = useAddPaymentMethod();
return ( <Card> <Formik initialValues={initialValues} enableReinitialize onSubmit={(formValues, formikHelpers) => { savePaymentType(formValues); formikHelpers.setSubmitting(false); }} > {({ submitForm, isSubmitting }) => ( <> <PaymentTypeGeneralDetails /> <PaymentTypeOptionsDetails /> <Box> <Grid container spacing={3} justify="flex-end"> <Grid item> <Button variant="contained" color="primary" isLoading={isSubmitting || isLoading} disabled={isSubmitting || isLoading} onClick={submitForm} > <FormattedMessage id="button.label.save" /> </Button> </Grid> </Grid> </Box> </> )} </Formik> </Card> );};
We can render as many subcomponents as we want. We can also group them in Accordion
s or Card
s or whatever the design might be.
#
2. Create form subcomponentsThese will each handle only part of the form and have access to the formik state via useFormikContext
.
"components/payment-types/details/PaymentTypeGeneralDetails.tsx
const PaymentTypeGeneralDetails = () => { const intl = useIntl();
const { values, setFieldValue, setFieldTouched } = useFormikContext<IPaymentType>();
const organizationUnitSets = useOrganizationUnitSets();
...
return ( <Box p={0}> <Box p={0} mb="8px"> <Field name="Name"> {({ meta, field }: FieldProps) => ( <Input required label={intl.formatMessage({ id: "payment-types.details.general-settings.label.name", })} {...field} value={field.value ?? ""} error={meta.touched && !!meta.error} helperText={meta.touched && !!meta.error ? meta.error : undefined} endIcon={ <ContextualHelpIcon label={intl.formatMessage({ id: "payment-types.details.general-settings.label.name", })} text={intl.formatMessage({ id: "payment-types.details.general-settings.label.name.help", })} /> } /> )} </Field> </Box> <Box p={0} mb="8px"> <Field name="Code"> {({ meta, field }: FieldProps) => ( <Input required label={intl.formatMessage({ id: "payment-types.details.general-settings.label.code", })} {...field} value={field.value ?? ""} error={meta.touched && !!meta.error} helperText={meta.touched && !!meta.error ? meta.error : undefined} endIcon={ <ContextualHelpIcon label={intl.formatMessage({ id: "payment-types.details.general-settings.label.code", })} text={intl.formatMessage({ id: "payment-types.details.general-settings.label.code.help", })} /> } /> )} </Field> </Box> <Box p={0} mb="8px"> <Field name="OrganizationUnitSetID"> {({ meta, field }: FieldProps) => ( <Autocomplete {...field} required={!field.value} label={intl.formatMessage({ id: "payment-types.details.general-settings.label.organization-unit-set-id", })} items={organizationUnitSets ?? []} matchKeys={["Name", "ID"]} optionIDKey="ID" renderOptionValueKey="Name" controlledSelectedItem={ organizationUnitSets?.find( (item) => item.ID === values?.OrganizationUnitSetID ) ?? "" } handleSelectedItem={(selected) => { setFieldValue("OrganizationUnitSetID", selected?.ID); setFieldTouched("OrganizationUnitSetID", true, false); }} error={!!meta.error && meta.touched} helperText={!!meta.error && meta.touched ? meta.error : undefined} /> )} </Field> </Box> ... <Box p={0} mb="8px"> <Field name="PrintOnDocuments"> {(props: FieldProps) => ( <Checkbox {...props.field} checked={props.field.value ?? false} onChange={(event) => setFieldValue("PrintOnDocuments", event.target.checked) } > <FormattedMessage id="payment-types.details.general-settings.label.print-on-documents" /> </Checkbox> )} </Field> </Box> <Box p={0} mb="8px"> <Field name="IsActive"> {(props: FieldProps) => ( <Checkbox {...props.field} checked={props.field.value ?? false} onChange={(event) => setFieldValue("IsActive", event.target.checked) } > <FormattedMessage id="payment-types.details.general-settings.label.is-active" /> </Checkbox> )} </Field> </Box> </Box> );};
"components/payment-types/details/PaymentTypeOptionsDetails.tsx
const PaymentTypeOptionsDetails = () => { const intl = useIntl();
const { values, setFieldValue } = useFormikContext<IPaymentType>();
const roleOptions = useUserRoleOptions();
...
return ( <Box p={0}> ... <Box p={0} mb="8px"> <Field name="Options.RequiredUserType"> {({ meta, field }: FieldProps) => ( <Select {...field} items={roleOptions ?? []} value={ field.value !== undefined && roleOptions?.length ? field.value : "" } onChange={(event) => setFieldValue("Options.RequiredUserType", event.target.value) } name="roles" label={intl.formatMessage({ id: "payment-types.details.options.label.role", })} fullWidth error={!!meta.error && meta.touched} helpertext={!!meta.error && meta.touched ? meta.error : undefined} /> )} </Field> </Box> ... <Box p={0} mb="8px"> <Field name="Options.CanBeCancelled"> {(props: FieldProps) => ( <Checkbox {...props.field} checked={props.field.value ?? false} onChange={(event) => setFieldValue("Options.CanBeCancelled", event.target.checked) } > <FormattedMessage id="payment-types.details.options.label.can-be-cancelled" /> </Checkbox> )} </Field> </Box> </Box> );};