admin管理员组文章数量:1287598
Basically the title. Anytime I try to select a value from the MultiSelect, I run into the infinite loop.
I get that once I update the form state, it triggers a re-render and propagates that down to the child components I just can't understand why I am getting infinite loop since my field.onChange
or alternatively form.setValue
is inside a callback in the child.
Any ideas?
Parent
import { useCallback } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { debounce } from "lodash";
import { useForm } from "react-hook-form";
import { z } from "zod";
import {
Form,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { MultiSelect } from "./multi-select";
const options = [
{ value: "la", label: "LA" },
{ value: "ny", label: "NY" },
];
const optionSchema = z.object({
label: z.string(),
value: z.string(),
});
const schema = z
.object({
cities: z.array(optionSchema),
})
.required();
export type FormValues = z.infer<typeof schema>;
type Props = {
handleSubmit: (data: Partial<FormValues>) => void;
};
export function CompanyProfileForm({ handleSubmit }: Props) {
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
cities: [],
},
values: {
cities: [],
},
mode: "onChange",
});
const debouncedSubmitField = useCallback(
debounce(async (fieldName: keyof FormValues) => {
// Validate only this field
const valid = await form.trigger(fieldName);
if (valid) {
const fieldValue = form.getValues(fieldName);
console.log("fieldName :>> ", fieldName);
console.log("fieldValue :>> ", fieldValue);
handleSubmit({ [fieldName]: fieldValue });
} else {
console.error(
`Validation failed for ${fieldName}:`,
form.formState.errors[fieldName]
);
}
}, 500),
[form.trigger, form.getValues, form.formState.errors, handleSubmit]
);
return (
<Form {...form}>
<form className="flex flex-col gap-4">
<FormField
control={form.control}
name="cities"
render={({ field }) => (
<FormItem className="flex flex-col w-full">
<FormLabel className="text-zinc-600 font-normal dark:text-zinc-400">
Cities
</FormLabel>
<MultiSelect
form={form}
field={field}
options={options}
formKey="cities"
/>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
);
}
Child
import { useCallback, useState } from "react";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@radix-ui/react-popover";
import { isEqual } from "lodash";
import { ChevronsUpDown } from "lucide-react";
import { cn } from "../../lib/utils";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { FormControl } from "@/components/ui/form";
type Option = { label: string; value: string };
type Props = {
field: any;
options: Option[];
debouncedSubmitField: (key: "cities") => void;
formKey: "cities";
};
const SelectedOptions = ({
selectedOptions,
}: {
selectedOptions: Option[];
}) => {
return selectedOptions.length
? selectedOptions.map((opt) => opt.label).join(", ")
: "Select location";
};
export const MultiSelect = ({
options,
field,
debouncedSubmitField,
formKey,
}: Props) => {
const [open, setOpen] = useState(false);
const selectedOptions: Option[] = field.value;
const handleSelectLocation = useCallback(
(location: Option) => {
let updatedOptions: Option[];
if (selectedOptions.find((opt) => opt.value === location.value)) {
updatedOptions = selectedOptions.filter(
(opt) => opt.value !== location.value
);
} else {
updatedOptions = [...selectedOptions, location];
}
if (!isEqual(selectedOptions, updatedOptions)) {
console.log("selectedOptions :>> ", selectedOptions);
console.log("updatedOptions :>> ", updatedOptions);
field.onChange(updatedOptions); // <=== causes infinite loop
// debouncedSubmitField(formKey);
}
},
[debouncedSubmitField, field, formKey, selectedOptions]
);
const isSelected = (location: Option) =>
!!selectedOptions.find((opt) => opt.value === location.value);
const atLeastOneOptionSelected = selectedOptions.length > 0;
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"justify-between w-full text-md",
!selectedOptions && "text-muted-foreground"
)}
>
<SelectedOptions selectedOptions={selectedOptions} />
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="popover-content-width-full">
<div className="w-full">
{options.map((option) => {
const isCurrentOptionSelected = isSelected(option);
const isDisabled =
!isCurrentOptionSelected && atLeastOneOptionSelected;
return (
<div
className="flex items-center justify-between cursor-pointer p-2 hover:bg-slate-50 hover:rounded-sm text-md w-full"
key={option.value}
onClick={() => {
if (!isDisabled) {
handleSelectLocation(option);
}
}}
>
<Checkbox checked={isSelected(option)} disabled={isDisabled} />
<span>{option.label}</span>
</div>
);
})}
</div>
</PopoverContent>
</Popover>
);
};
版权声明:本文标题:reactjs - react hook form - form.setValue or field.onChange causes infinite loop in child component - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741234696a2362776.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论