reactjs - Hvordan lage en gjenbrukbar react-hook-form-komponent med TypeScript-generics?
Person-komponenten gjenbrukes i to forskjellige former:
Person.tsx
import { UseFormReturn } from "react-hook-form";
import { FieldPaths } from "./types";
type Props<TFormValues> = {
methods: UseFormReturn<TFormValues>;
fieldPaths: FieldPaths<TFormValues>;
};
const Person = <TFormValues,>({
methods,
fieldPaths
}: Props<TFormValues>) => {
const { register } = methods;
return (
<div className="person">
<div className="name-field">
<input {...register(fieldPaths.firstName)} placeholder="First name" />
<input {...register(fieldPaths.lastName)} placeholder="Last name" />
</div>
<div className="radio-group">
<label>
<input type="radio" value="free" {...register(fieldPaths.status)} />{" "}
Free
</label>
<label>
<input type="radio" value="busy" {...register(fieldPaths.status)} />{" "}
Busy
</label>
</div>
</div>
);
};
export { Person };
SimpleForm.tsx
import { useForm } from "react-hook-form";
import { Person } from "./Person";
import { TPerson } from "./types";
import { emptyPerson } from "./utils";
type SimpleFormValues = {
person: TPerson;
};
const simpleFormFieldPaths = {
firstName: "person.name.first",
lastName: "person.name.last",
status: "person.status"
} as const;
const SimpleForm = () => {
const methods = useForm<SimpleFormValues>({
defaultValues: {
person: emptyPerson
}
});
const { handleSubmit } = methods;
const onSubmit = ({ person }: SimpleFormValues) => {
console.log(JSON.stringify(person, null, 2));
};
return (
<div>
<h2>Simple form</h2>
<form method="post" onSubmit={handleSubmit(onSubmit)}>
<Person methods={methods} fieldPaths={simpleFormFieldPaths} />
<button type="submit">Submit</button>
</form>
</div>
);
};
export { SimpleForm };
ComplexForm.tsx
import { useFieldArray, useForm } from "react-hook-form";
import { Person } from "./Person";
import { TPerson } from "./types";
import { emptyPerson } from "./utils";
type ComplexFormValues = {
artists: TPerson[];
};
const ComplexForm = () => {
const methods = useForm<ComplexFormValues>({
defaultValues: {
artists: [emptyPerson, emptyPerson, emptyPerson]
}
});
const { control, handleSubmit } = methods;
const { fields } = useFieldArray({
control,
name: "artists"
});
const onSubmit = ({ artists }: ComplexFormValues) => {
console.log(JSON.stringify(artists, null, 2));
};
return (
<div>
<h2>Complex form</h2>
<form method="post" onSubmit={handleSubmit(onSubmit)}>
{fields.map((field, index) => {
const complexFormFieldPaths = {
firstName: `artists.${index}.name.first`,
lastName: `artists.${index}.name.last`,
status: `artists.${index}.status`
} as const;
return (
<Person
methods={methods}
fieldPaths={complexFormFieldPaths}
key={field.id}
/>
);
})}
<button type="submit">Submit</button>
</form>
</div>
);
};
export { ComplexForm };
types.ts
import { FieldPath } from "react-hook-form";
export type FieldPaths<TFormValues> = {
firstName: FieldPath<TFormValues>;
lastName: FieldPath<TFormValues>;
status: FieldPath<TFormValues>;
};
export type TPerson = {
name: {
first: string;
last: string;
};
status: "" | "free" | "busy";
};
utils.ts
import { TPerson } from "./types";
export const emptyPerson: TPerson = {
name: {
first: "",
last: ""
},
status: ""
};
Det fungerer vakkert, men hvis jeg prøver å setValue i Person, kaster TypeScript en feil:
const { setValue } = methods;
setValue(fieldPaths.firstName, "David");
Argument av typen 'string' kan ikke tildeles parameter av typen 'UnpackNestedValue
>'
Hvordan kunne jeg fikse dette?
CodeSandbox
Jeg tror det jeg vil, er å antyde til TypeScript at PathValue(TFormValues, typeof fieldPaths.firstName) alltid kommer til å være en string, og da bør det fungere, men jeg er ikke sikker på hvordan jeg skal oppnå det.
Merk: PathValue er definert her.
Svar #1
Ser ut som dette pleide å være fikset i slutten av react-hook-form@6; deretter kom v7 som gjeninnførte problemet og ser ut til å ha blitt videreført til v8 beta også.
Når det gjelder spørsmålet ditt
"gi TypeScript et hint om at
PathValue(TFormValues, typeof fieldPaths.firstName)alltid kommer til å være enstring"
Dette vil sannsynligvis ikke fungere, på grunn av hva setValue er definert til å akseptere.
Ideelt sett ville vi presset spørsmålet på forfatterne og håpet på en rask løsning, men i fravær av det, finnes det to løsninger her:
Enten alias
UnpackNestedValueog kast til detimport { UnpackNestedValue, PathValue, Path } from "react-hook-form"; type ValueArgument<f> = UnpackNestedValue<PathValue<f, Path<f>>>; setValue(fieldPaths.firstName, "David" as ValueArgument<TFormValues>);Utvid typene hele veien opp til å akseptere primitive typer
import { UseFormReturn, FieldValues, FieldPath, UnpackNestedValue, FieldPathValue, SetValueConfig, } from "react-hook-form"; import { FieldPaths } from "./types"; type UseMyFormSetValue<TFieldValues extends FieldValues> = < TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> >( name: TFieldName, value: UnpackNestedValue<FieldPathValue<TFieldValues, TFieldName>> | string, options?: SetValueConfig ) => void; interface UseMyFormReturn<TFormValues> extends UseFormReturn<TFormValues> { setValue: UseMyFormSetValue<TFormValues>; } type Props<TFormValues> = { methods: UseMyFormReturn<TFormValues>; fieldPaths: FieldPaths<TFormValues>; }; const Person = <TFormValues>({ methods, fieldPaths }: Props<TFormValues>) => { const { register, setValue } = methods; const changeName = () => { setValue(fieldPaths.firstName, "David"); };
