Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix types for react-hook-form 7.45 #1997

Merged
merged 2 commits into from
Jul 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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