Reputation: 89
I am creating a reusable multiselect combobox component using shadcn and typescript and everything is properly in simple place (directly using in page) . but I get an error when using in complicated form (using inside a dialog or other place) :
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
NextJs code:
"use client";
import * as React from "react";
import { ChevronDown, CircleX, Search, X } from "lucide-react";
import { cn, GetColor } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ComboBoxItems, TComboBox } from "@/types";
import { Checkbox } from "./checkbox";
import { FC } from "react";
export default function Combobox(props: TComboBox) {
const {
items,
placeholder,
searchable,
multiSelect,
values,
onChange,
showError,
} = props;
const [open, setOpen] = React.useState(false);
const [serachKeyword, setSearchKeyword] = React.useState<
string | undefined
>();
const[data,setData]=React.useState<string[]|undefined>()
const filteredItems = items.filter(
(item) => item.label!.includes(serachKeyword || "") ?? item
);
const getSelectedColors = items
.filter((s) => values?.includes(s.value!))
.map((s) => ({ color: s.color, value: s.value }));
const handleChanges = (value: string) => {
const findedItem = values?.find((s) => s == value);
if (!multiSelect && values?.length == 1 && !findedItem) {
setOpen(false);
return;
}
if (findedItem) {
const allExceptValue = values.filter((s) => s != value);
onChange(allExceptValue);
} else {
const fixValues = values ? [...values, value] : [value];
onChange(fixValues);
}
};
return (
<React.Fragment>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild className={`ComboBoxTrigger`}>
<Button
variant="white"
role="combobox"
aria-expanded={open}
className={cn(
"w-full justify-between !h-[67px] hover:bg-white",
showError?"!border-red-500":null
)}
>
{placeholder}
<ChevronDown
strokeWidth={1.6}
className={cn("transition-all", open && "rotate-180")}
/>
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="ComboboxContent">
{searchable && <ComboboxSearch onType={setSearchKeyword} />}
{filteredItems.map((item) => (
<ComboxItem
key={item.value}
checked={values?.includes(item.value!)}
{...item}
callback={handleChanges}
/>
))}
</PopoverContent>
</Popover>
<Colors values={getSelectedColors} onClose={handleChanges} />
</React.Fragment>
);
}
const Colors: FC<{
values: Array<{ color: string | undefined; value: string | undefined }>;
onClose: (val: string) => void;
}> = ({ values, onClose }) => {
return (
values != undefined && (
<div className="mt-5 flex gap-3 py-2">
{values?.map(({ color, value }, index) => (
<span
key={index}
style={{ background: GetColor(color!) }}
className="min-w-8 min-h-8 rounded-sm relative"
>
<X
onClick={() => onClose(value!)}
className="absolute -top-2 -left-2 text-xs bg-red-500 rounded-full text-white cursor-pointer p-1 h-5 w-5"
/>
</span>
))}
</div>
)
);
};
const ComboxItem: React.FC<
Partial<ComboBoxItems> & {
callback: (val: string) => void;
checked?: boolean;
}
> = (props) => {
const { checkbox, label, color, value, callback, checked } = props;
return (
<div className="flex items-center gap-2 " onClick={() => callback(value!)}>
{checkbox && <Checkbox checked={checked} />}
{color && (
<span
style={{ background: GetColor(color!) }}
className="w-8 h-8 rounded-sm"
></span>
)}
<span className="whitespace-nowrap text-lg">{label}</span>
</div>
);
};
const ComboboxSearch: React.FC<{ onType: (keyword: string) => void }> = ({
onType,
}) => {
return (
<div className="flex items-center gap-2 relative">
<Search className="size-6 text-gray-500" />
<input
onChange={({ target }) => onType(target.value)}
type="text"
className="w-full py-2 px-3 rounded outline-none"
placeholder="جستجو..."
/>
</div>
);
};
This is how I use it.:
const {
errors,
handleSubmit,
handleBlur,
handleChange,
touched,
setFieldValue,
values,
setFieldTouched,
} = useFormik<ICreateEditProductForm>({
initialValues: {
colors: [],
},
validationSchema: createEditProductSchema,
onSubmit: (values) => {
console.log(values);
},
});
<Combobox
items={colors}
onChange={(selectedColors) => {
setFieldValue("colors", selectedColors);
}}
placeholder="انتخاب رنگ"
values={values.colors}
multiSelect={true}
searchable
showError={touched.colors && errors.colors !== undefined}
/>
So how can I prevent error?
Upvotes: -1
Views: 36