Skip to content

Commit

Permalink
Merge pull request #4296 from dfe-analytical-services/EES-4435
Browse files Browse the repository at this point in the history
EES-4435 replace Formik with RHF in front end
  • Loading branch information
amyb-hiveit authored Sep 20, 2023
2 parents 434ed83 + 0623665 commit 4cfcb5f
Show file tree
Hide file tree
Showing 22 changed files with 2,238 additions and 351 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, {
memo,
MouseEventHandler,
ReactNode,
Ref,
} from 'react';

export interface FormBaseInputProps extends FormLabelProps {
Expand All @@ -20,6 +21,7 @@ export interface FormBaseInputProps extends FormLabelProps {
hint?: string;
id: string;
inputMode?: HTMLAttributes<HTMLInputElement>['inputMode'];
inputRef?: Ref<HTMLInputElement>;
list?: string;
name: string;
width?: 20 | 10 | 5 | 4 | 3 | 2;
Expand All @@ -44,6 +46,7 @@ const FormBaseInput = ({
hideLabel,
id,
inputMode,
inputRef,
label,
labelSize,
width,
Expand All @@ -66,6 +69,7 @@ const FormBaseInput = ({
})}
id={id}
inputMode={inputMode}
ref={inputRef}
type={type}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React, {
CSSProperties,
FocusEventHandler,
ReactNode,
Ref,
} from 'react';
import ErrorMessage from '../ErrorMessage';

Expand All @@ -25,6 +26,7 @@ export interface FormSelectProps extends FormLabelProps {
error?: string;
id: string;
inline?: boolean;
inputRef?: Ref<HTMLSelectElement>;
hint?: string;
name: string;
onBlur?: FocusEventHandler;
Expand All @@ -45,6 +47,7 @@ const FormSelect = ({
error,
id,
inline = false,
inputRef,
hint,
hideLabel,
label,
Expand Down Expand Up @@ -93,6 +96,7 @@ const FormSelect = ({
id={id}
name={name}
disabled={disabled}
ref={inputRef}
onBlur={onBlur}
onChange={onChange}
value={value}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import useRegister from '@common/components/form/rhf/hooks/useRegister';
import getErrorMessage from '@common/components/form/rhf/util/getErrorMessage';
import { useFormIdContext } from '@common/components/form/contexts/FormIdContext';
import FormGroup from '@common/components/form/FormGroup';
import React, {
ChangeEvent,
ChangeEventHandler,
ComponentType,
ReactNode,
useMemo,
} from 'react';
import {
FieldValues,
FieldElement,
Path,
useFormContext,
ChangeHandler,
} from 'react-hook-form';

export type RHFFormFieldComponentProps<Props, TFormValues> =
FormFieldProps<TFormValues> &
Omit<Props, 'name' | 'id' | 'value' | 'error'> & {
id?: string;
name: Path<TFormValues>;
};

export interface FormFieldProps<TFormValues> {
id?: string;
formGroup?: boolean;
formGroupClass?: string;
name: Path<TFormValues>;
showError?: boolean;
}

interface FormFieldInputProps {
error?: ReactNode | string;
id: string;
}

type InternalFormFieldProps<P> = Omit<P, 'id' | 'value' | 'error'> & {
as?: ComponentType<FormFieldInputProps & P>;
children?:
| ((props: {
id: string;
field: FieldElement & { error?: string };
}) => ReactNode)
| ReactNode;
id?: string;
type?: 'checkbox' | 'radio' | 'text' | 'number';
};

export default function RHFFormField<
Value extends FieldValues,
Props = Record<string, unknown>,
>({
as,
children,
formGroup = true,
formGroupClass,
id: customId,
name,
showError = true,
...props
}: FormFieldProps<Value> & InternalFormFieldProps<Props>) {
const {
formState: { errors },
register,
} = useFormContext<Value>();

const { ref: inputRef, ...field } = useRegister(name, register);
const { fieldId } = useFormIdContext();
const id = fieldId(name, customId);

const error = getErrorMessage(errors, name, showError);

const component = useMemo(() => {
const typedProps = props as {
onBlur?: ChangeHandler;
onChange?: ChangeEventHandler;
};
const Component = as as ComponentType<FormFieldInputProps>;

if (Component) {
return (
<Component
{...props}
{...field}
id={fieldId(name, customId)}
name={name}
error={error}
inputRef={inputRef}
onChange={(event: ChangeEvent) => {
if (typedProps.onChange) {
typedProps.onChange(event);
}

if (!event.isDefaultPrevented()) {
field.onChange(event);
}
}}
onBlur={(event: FocusEvent) => {
if (typedProps.onBlur) {
typedProps.onBlur(event);
}
if (!event.defaultPrevented) {
field.onBlur(event);
}
}}
/>
);
}

return typeof children === 'function'
? children({
id,
field: { ...field, error },
})
: children;
}, [
as,
children,
customId,
error,
field,
fieldId,
id,
inputRef,
name,
props,
]);

return formGroup ? (
<FormGroup hasError={!!error} className={formGroupClass}>
{component}
</FormGroup>
) : (
component
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import FormCheckboxGroup, {
CheckboxOption,
FormCheckboxGroupProps,
} from '@common/components/form/FormCheckboxGroup';
import { useFormIdContext } from '@common/components/form/contexts/FormIdContext';
import useRegister from '@common/components/form/rhf/hooks/useRegister';
import handleAllRHFCheckboxChange from '@common/components/form/rhf/util/handleAllRHFCheckboxChange';
import getErrorMessage from '@common/components/form/rhf/util/getErrorMessage';
import React from 'react';
import { FieldValues, Path, useFormContext, useWatch } from 'react-hook-form';

export interface Props<TFormValues extends FieldValues>
extends Omit<FormCheckboxGroupProps, 'name' | 'value' | 'id'> {
name: Path<TFormValues>;
id?: string;
options: CheckboxOption[];
showError?: boolean;
}

export default function RHFFormFieldCheckboxGroup<
TFormValues extends FieldValues,
>({
name,
id: customId,
options,
showError = true,
...props
}: Props<TFormValues>) {
const {
formState: { errors, submitCount },
register,
setValue,
trigger,
} = useFormContext<TFormValues>();

const { ref: inputRef, ...field } = useRegister(name, register);
const { fieldId } = useFormIdContext();
const id = fieldId(name, customId);
const selectedValues = useWatch({ name }) || [];
const { onAllChange, onChange } = props;

return (
<FormCheckboxGroup
{...props}
{...field}
error={getErrorMessage(errors, name, showError)}
id={id}
inputRef={inputRef}
options={options}
value={selectedValues}
onAllChange={(event, checked) => {
onAllChange?.(event, checked, options);

if (event.isDefaultPrevented()) {
return;
}

handleAllRHFCheckboxChange({
checked,
name,
options,
selectedValues,
setValue,
submitCount,
trigger,
});
}}
onChange={(event, option) => {
onChange?.(event, option);

if (event.isDefaultPrevented()) {
return;
}

field.onChange(event);
}}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import DetailsMenu from '@common/components/DetailsMenu';
import { FormFieldCheckboxSearchGroupProps } from '@common/components/form/FormFieldCheckboxSearchGroup';
import RHFFormCheckboxSelectedCount from '@common/components/form/rhf/RHFFormCheckboxSelectedCount';
import RHFFormFieldCheckboxSearchGroup from '@common/components/form/rhf/RHFFormFieldCheckboxSearchGroup';
import RHFFormFieldCheckboxGroup from '@common/components/form/rhf/RHFFormFieldCheckboxGroup';
import get from 'lodash/get';
import React, { useEffect, useState } from 'react';
import { FieldValues, useFormContext } from 'react-hook-form';

interface Props<TFormValues extends FieldValues>
extends FormFieldCheckboxSearchGroupProps<TFormValues> {
legend: string;
open?: boolean;
}

export default function RHFFormFieldCheckboxMenu<
TFormValues extends FieldValues,
>(props: Props<TFormValues>) {
const { name, open: defaultOpen = false, options, legend } = props;
const [open, setOpen] = useState(defaultOpen);

const {
formState: { errors },
} = useFormContext();

useEffect(() => {
if (!open && get(errors, name)) {
setOpen(true);
}
}, [errors, name, open, setOpen]);

return (
<DetailsMenu
open={open}
jsRequired
summary={legend}
summaryAfter={<RHFFormCheckboxSelectedCount name={name} />}
>
{options.length > 1 ? (
<RHFFormFieldCheckboxSearchGroup
selectAll
legendHidden
{...props}
name={name}
options={options}
/>
) : (
<RHFFormFieldCheckboxGroup {...props} name={name} options={options} />
)}
</DetailsMenu>
);
}
Loading

0 comments on commit 4cfcb5f

Please sign in to comment.