reactjs - Hur skapar man en återanvändbar react-hook-form-komponent med TypeScript-generics?
Person-komponenten återanvänds i två olika 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 fungerar utmärkt, men om jag försöker att setValue i Person, kastar TypeScript ett fel:
const { setValue } = methods;
setValue(fieldPaths.firstName, "David");
Argument av typen 'string' kan inte tilldelas till parametern av typen 'UnpackNestedValue
>'
Hur skulle jag kunna fixa detta?
CodeSandbox
Jag tror att det jag vill är att antyda för TypeScript att PathValue(TFormValues, typeof fieldPaths.firstName) alltid kommer att vara en string, och då borde det fungera, men jag är inte säker på hur man uppnår det.
Observera: PathValue definieras här.
Svar #1
Det ser ut som att detta brukade vara fixat i slutet av react-hook-form@6; sedan kom v7 som återinförde problemet och verkar ha förts vidare till v8 beta också.
Angående din fråga
"Ange för TypeScript att
PathValue(TFormValues, typeof fieldPaths.firstName)alltid kommer att vara enstring"
Detta kommer sannolikt inte att fungera, på grund av vad setValue har definierats att acceptera.
I idealfallet skulle vi ta upp frågan med författarna och hoppas på en snabb lösning, men i avsaknad av det finns det två lösningar här:
Antingen alias
UnpackNestedValueoch kasta till detimport { UnpackNestedValue, PathValue, Path } from "react-hook-form"; type ValueArgument<f> = UnpackNestedValue<PathValue<f, Path<f>>>; setValue(fieldPaths.firstName, "David" as ValueArgument<TFormValues>);Utöka typerna hela vägen upp för att acceptera primitiva 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"); };
