Skip to content

Commit

Permalink
Merge pull request #1997 from quantified-uncertainty/rhf-fixes
Browse files Browse the repository at this point in the history
Fix types for react-hook-form 7.45
  • Loading branch information
berekuk authored Jul 1, 2023
2 parents 796bd5e + 84a092b commit 07259a8
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ const HTMLForm: FC = () => {
label: item.id,
value: item.id,
}))}
onChange={(item) => field.onChange(item?.value)}
onChange={(item) => field.onChange(item?.value ?? null)}
isClearable={true}
/>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"framer-motion": "^10.12.18",
"react-colorful": "^5.6.1",
"react-hook-form": "^7.45.1",
"react-textarea-autosize": "^8.5.0",
"react-textarea-autosize": "8.4.1",
"react-use": "^17.4.0"
},
"devDependencies": {
Expand Down
22 changes: 16 additions & 6 deletions packages/ui/src/forms/common/ControlledFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
import { ReactNode } from "react";
import {
ControllerRenderProps,
FieldPath,
FieldPathByValue,
FieldValues,
RegisterOptions,
} from "react-hook-form";

import { ControlledFormInput } from "./ControlledFormInput.js";
import { FieldLayout, FormFieldLayoutProps } from "./FormFieldLayout.js";
import { PatchedControllerRenderProps } from "./types.js";

export type ControlledFormFieldProps<
TValues extends FieldValues,
TName extends FieldPath<TValues> = FieldPath<TValues>
TValueType = unknown,
TName extends FieldPathByValue<TValues, TValueType> = FieldPathByValue<
TValues,
TValueType
>
> = FormFieldLayoutProps & {
name: TName;
rules?: RegisterOptions<TValues, TName>;
children: (props: ControllerRenderProps<TValues, TName>) => ReactNode;
children: (
props: PatchedControllerRenderProps<TValues, TValueType, TName>
) => ReactNode;
};

export function ControlledFormField<
TValues extends FieldValues,
TName extends FieldPath<TValues> = FieldPath<TValues>
TValueType,
TName extends FieldPathByValue<TValues, TValueType> = FieldPathByValue<
TValues,
TValueType
>
>({
name,
rules,
label,
description,
inlineLabel,
children,
}: ControlledFormFieldProps<TValues, TName>) {
}: ControlledFormFieldProps<TValues, TValueType, TName>) {
return (
<FieldLayout
label={label}
Expand Down
34 changes: 28 additions & 6 deletions packages/ui/src/forms/common/ControlledFormInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,40 @@ import { ReactNode } from "react";
import {
Controller,
ControllerRenderProps,
FieldPath,
FieldPathByValue,
FieldPathValue,
FieldValues,
UseControllerProps,
useFormContext,
} from "react-hook-form";

import { WithRHFError } from "./WithRHFError.js";
import { PatchedControllerRenderProps } from "./types.js";

type Props<
TValues extends FieldValues,
TName extends FieldPath<TValues> = FieldPath<TValues>
TValueType,
TName extends FieldPathByValue<TValues, TValueType> = FieldPathByValue<
TValues,
TValueType
>
> = {
name: TName;
rules?: UseControllerProps<TValues, TName>["rules"];
children: (props: ControllerRenderProps<TValues, TName>) => ReactNode;
children: (
props: PatchedControllerRenderProps<TValues, TValueType, TName>
) => ReactNode;
};

// Helper component for custom controlled react-hook-form connected components.
export function ControlledFormInput<
TValues extends FieldValues,
TName extends FieldPath<TValues> = FieldPath<TValues>
>({ name, rules, children }: Props<TValues, TName>) {
TValueType,
TName extends FieldPathByValue<TValues, TValueType> = FieldPathByValue<
TValues,
TValueType
>
>({ name, rules, children }: Props<TValues, TValueType, TName>) {
const { control } = useFormContext<TValues>();

return (
Expand All @@ -32,7 +44,17 @@ export function ControlledFormInput<
name={name}
control={control}
rules={rules}
render={({ field }) => <div>{children(field) ?? null}</div>}
render={({ field }) => (
<div>
{children(
// our controller components don't allow ChangeEvent events;
// also, Typescript is not smart enough to infer that FieldPathByValue is the inverse of FieldPathValue
field as Omit<typeof field, "onChange"> & {
onChange: (event: TValueType) => void;
}
)}
</div>
)}
/>
</WithRHFError>
);
Expand Down
29 changes: 26 additions & 3 deletions packages/ui/src/forms/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { FieldPath, FieldValues, RegisterOptions } from "react-hook-form";
import {
ControllerRenderProps,
FieldPath,
FieldPathByValue,
FieldValues,
RegisterOptions,
} from "react-hook-form";
import { FormFieldLayoutProps } from "./FormFieldLayout.js";

type StringRules<
Expand All @@ -18,15 +24,21 @@ type NumberRules<
// These types is useful for specific field/*FormField declarations.
export type CommonStringFieldProps<
TValues extends FieldValues,
TName extends FieldPath<TValues> = FieldPath<TValues>
TName extends FieldPathByValue<TValues, string> = FieldPathByValue<
TValues,
string
>
> = Omit<FormFieldLayoutProps, "inlineLabel"> & {
name: TName;
rules?: StringRules<TValues, TName>;
};

export type CommonNumberFieldProps<
TValues extends FieldValues,
TName extends FieldPath<TValues> = FieldPath<TValues>
TName extends FieldPathByValue<
TValues,
number | undefined
> = FieldPathByValue<TValues, number | undefined>
> = Omit<FormFieldLayoutProps, "inlineLabel"> & {
name: TName;
rules?: NumberRules<TValues, TName>;
Expand All @@ -40,3 +52,14 @@ export type CommonUnknownFieldProps<
// TODO - allow more rules?
rules?: Pick<RegisterOptions<TValues, TName>, "required" | "validate">;
};

export type PatchedControllerRenderProps<
TValues extends FieldValues,
TValueType,
TName extends FieldPathByValue<TValues, TValueType> = FieldPathByValue<
TValues,
TValueType
>
> = Omit<ControllerRenderProps<TValues, TName>, "onChange"> & {
onChange: (event: TValueType) => void;
};
11 changes: 7 additions & 4 deletions packages/ui/src/forms/fields/ColorFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { FieldPath, FieldValues } from "react-hook-form";
import { FieldPathByValue, FieldValues } from "react-hook-form";

import { ControlledFormField } from "../common/ControlledFormField.js";
import { StyledColorInput } from "../styled/StyledColorInput.js";
import { CommonStringFieldProps } from "../common/types.js";
import { StyledColorInput } from "../styled/StyledColorInput.js";

export function ColorFormField<
TValues extends FieldValues,
TName extends FieldPath<TValues> = FieldPath<TValues>
TName extends FieldPathByValue<TValues, string> = FieldPathByValue<
TValues,
string
>
>({ ...fieldProps }: CommonStringFieldProps<TValues, TName>) {
return (
<ControlledFormField {...fieldProps} inlineLabel>
<ControlledFormField<TValues, string, TName> {...fieldProps} inlineLabel>
{({ value, onChange }) => (
<StyledColorInput value={value} onChange={onChange} />
)}
Expand Down
7 changes: 5 additions & 2 deletions packages/ui/src/forms/fields/NumberFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { FieldPath, FieldValues } from "react-hook-form";
import { FieldPathByValue, FieldValues } from "react-hook-form";

import { ControlledFormField } from "../common/ControlledFormField.js";
import { CommonNumberFieldProps } from "../common/types.js";
import { StyledInput, type StyledInputProps } from "../styled/StyledInput.js";

export function NumberFormField<
TValues extends FieldValues,
TName extends FieldPath<TValues> = FieldPath<TValues>
TName extends FieldPathByValue<
TValues,
number | undefined
> = FieldPathByValue<TValues, number | undefined>
>({
placeholder,
rules = {},
Expand Down
7 changes: 5 additions & 2 deletions packages/ui/src/forms/fields/TextAreaFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FieldPath, FieldValues } from "react-hook-form";
import { FieldPathByValue, FieldValues } from "react-hook-form";

import { FormField } from "../common/FormField.js";
import { CommonStringFieldProps } from "../common/types.js";
Expand All @@ -9,7 +9,10 @@ import {

export function TextAreaFormField<
TValues extends FieldValues,
TName extends FieldPath<TValues> = FieldPath<TValues>
TName extends FieldPathByValue<TValues, string> = FieldPathByValue<
TValues,
string
>
>({
placeholder,
...fieldProps
Expand Down
7 changes: 5 additions & 2 deletions packages/ui/src/forms/fields/TextFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { FieldPath, FieldValues } from "react-hook-form";
import { FieldPathByValue, FieldValues } from "react-hook-form";

import { FormField } from "../common/FormField.js";
import { CommonStringFieldProps } from "../common/types.js";
import { StyledInput, type StyledInputProps } from "../styled/StyledInput.js";

export function TextFormField<
TValues extends FieldValues,
TName extends FieldPath<TValues> = FieldPath<TValues>
TName extends FieldPathByValue<TValues, string> = FieldPathByValue<
TValues,
string
>
>({
placeholder,
size,
Expand Down
7 changes: 6 additions & 1 deletion packages/ui/src/forms/styled/StyledTextArea.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { TextareaHTMLAttributes, forwardRef } from "react";
import TextareaAutosize from "react-textarea-autosize";
import ImportedTextareaAutosize from "react-textarea-autosize";
import { clsx } from "clsx";

// ESM hack; can be removed after we upgrade to >8.5.0
// but that depends on https://github.com/Andarist/react-textarea-autosize/issues/379
const TextareaAutosize =
ImportedTextareaAutosize as unknown as typeof ImportedTextareaAutosize.default;

export type StyledTextAreaProps = Omit<
TextareaHTMLAttributes<HTMLTextAreaElement>,
"className" | "style"
Expand Down
17 changes: 2 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4509,7 +4509,7 @@ __metadata:
react-colorful: ^5.6.1
react-dom: ^18.2.0
react-hook-form: ^7.45.1
react-textarea-autosize: ^8.5.0
react-textarea-autosize: 8.4.1
react-use: ^17.4.0
rollup-plugin-node-builtins: ^2.1.2
storybook: ^7.0.24
Expand Down Expand Up @@ -19627,7 +19627,7 @@ __metadata:
languageName: node
linkType: hard

"react-textarea-autosize@npm:^8.3.2":
"react-textarea-autosize@npm:8.4.1, react-textarea-autosize@npm:^8.3.2":
version: 8.4.1
resolution: "react-textarea-autosize@npm:8.4.1"
dependencies:
Expand All @@ -19640,19 +19640,6 @@ __metadata:
languageName: node
linkType: hard

"react-textarea-autosize@npm:^8.5.0":
version: 8.5.0
resolution: "react-textarea-autosize@npm:8.5.0"
dependencies:
"@babel/runtime": ^7.20.13
use-composed-ref: ^1.3.0
use-latest: ^1.2.1
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
checksum: c5fde6100b74277d75a796a7b3b2287759ed16173c34b58cfa541bca474a09d23e7695f6c9aab4e9b5f35cb4f05792d5e0fb669d926efba536f82c7b7b75e5e4
languageName: node
linkType: hard

"react-transition-group@npm:^4.3.0":
version: 4.4.5
resolution: "react-transition-group@npm:4.4.5"
Expand Down

1 comment on commit 07259a8

@vercel
Copy link

@vercel vercel bot commented on 07259a8 Jul 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.