基本上我的问题是,每次我向颜色选择器输入添加值或添加图像时,它都会重新渲染,我已经坚持了好几天了。
// ColorPicker component
"use client";
import { useCallback, useEffect, useState } from "react";
import SelectImage from "./SelectImage";
interface ColorPickerProps {
variable: colorPickerProps;
addInputToState: (value: colorPickerProps) => void;
removeVariableFromState: (value: colorPickerProps) => void;
isProductCreated: boolean;
}
const ColorPicker: React.FC<ColorPickerProps> = ({
addInputToState,
removeVariableFromState,
isProductCreated,
variable,
}: ColorPickerProps) => {
const [isSelected, setIsSelected] = useState(false);
const [File, setFile] = useState<File | null>(null);
const [price, setPrice] = useState(0);
const [Quantity, setQuantity] = useState(0);
useEffect(() => {
if (isProductCreated) {
setIsSelected(false);
setFile(null);
}
}, [isProductCreated]);
const handleFileChange = useCallback(
(value: File) => {
setFile(value);
addInputToState({ ...variable, image: value });
},
[addInputToState, variable]
);
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
let Numbervalue = parseInt(value);
if (name === "price") {
if (!Numbervalue || Numbervalue < 0) Numbervalue = 0;
if (Numbervalue > 1000000) Numbervalue = 1000000;
setPrice(Numbervalue);
addInputToState({ ...variable, price: Numbervalue });
}
if (name === "stock") {
if (!Numbervalue || Numbervalue < 0) Numbervalue = 0;
if (Numbervalue > 1000) Numbervalue = 1000;
setQuantity(Numbervalue);
addInputToState({ ...variable, stock: Numbervalue });
}
},
[addInputToState, variable]
);
const handleCheck = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setIsSelected(e.target.checked);
if (!e.target.checked) {
setFile(null);
removeVariableFromState(variable);
}
},
[removeVariableFromState, variable]
);
return (
<div className="flex flex-col gap-4 py-6 min-h-[5rem] text-center justify-center items-center rounded-md border-solid border-base-200 border-2 hover:border-base-content duration-500">
{/* ... rest of the component ... */}
</div>
);
};
export default React.memo(ColorPicker);
整个表单组件:
"use client";
import toast from "react-hot-toast";
import CategoriesCard from "./components/CategoriesCard";
import ColorPicker from "./components/ColorPicker";
import { currentUserType } from "@/utils/interfaces/types";
import { zodResolver } from "@hookform/resolvers/zod";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { FieldValues, useForm } from "react-hook-form";
import { v4 } from "uuid";
import { z } from "zod";
import { categories } from "./objects/categories";
import { variables } from "./objects/variables";
const variableSchema = z.object({
color: z.string(),
colorCode: z.string(),
stock: z.number(),
price: z.number(),
image: z.string(),
});
const schema = z.object({
name: z.string(),
description: z.string(),
brand: z.string(),
category: z.string(),
variables: z.array(variableSchema),
});
export interface colorPickerProps {
color: string;
colorCode: string;
stock: number;
price: number;
image: File | null;
}
export default function AddMenu({ user }: { user: currentUserType | null }) {
//Router Config
const router = useRouter();
//Check if Authorized
useEffect(() => {
if (!user || user.role !== "ADMIN") {
router.push("/");
toast.error("Acesso Negado");
return;
} else if (user.role === "ADMIN") {
setIsAuth(true);
}
}, [router, user]);
//States
const [isAuth, setIsAuth] = useState(false);
const [items, setItems] = useState<colorPickerProps[]>([]);
const [isCreated, setIsCreated] = useState(false);
//Form Config
const {
register,
handleSubmit,
setValue,
watch,
reset,
formState: { errors },
} = useForm({ resolver: zodResolver(schema) });
//Form Submit
function onSubmit(data: FieldValues) {}
//State to Form Data
function removeVariableFromState(value: colorPickerProps) {
setItems((prev) => {
const filteredItems = prev?.filter((item) => item.color !== value.color);
if (prev) {
return filteredItems;
}
return prev;
});
}
const addInputToState = useCallback(
(value: colorPickerProps) => {
setItems((prevItems) => {
if (!prevItems) {
return [value];
}
const foundItemIndex = prevItems.findIndex(
(item) => item.color === value.color
);
if (foundItemIndex === -1) {
return [...prevItems, value];
}
const newArray = [...prevItems];
newArray[foundItemIndex] = value;
return newArray;
});
},
[setItems]
);
//set Custom Form Value
const setCustomValue = useCallback(
(id: string, value: any) => {
setValue(id, value, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
},
[setValue]
);
// Listen Changes Effects
useEffect(() => {
setCustomValue("variables", items);
}, [items, setCustomValue]);
useEffect(() => {
if (isCreated) {
reset();
setItems([]);
setIsCreated(false);
}
}, [isCreated, reset]);
//Watch Events
const category = watch("category");
return (
<div className="w-3/4 h-3/4">
{isAuth && (
<form
className="flex flex-col shadow-2xl rounded-xl my-4 mx-[13rem] py-8 px-[5rem] gap-5 "
onSubmit={handleSubmit(onSubmit)}
>
<input
className="rounded-md input border-base-300"
placeholder="Nome do Produto"
type="text"
{...register("name")}
/>
<textarea
className="textarea border-base-300"
placeholder="Descrição do Produto"
id="description"
cols={10}
rows={5}
{...register("description")}
></textarea>
<input
className="rounded-md input border-base-300"
placeholder="Marca do Produto"
type="text"
{...register("brand")}
/>
<div className="grid grid-cols-3 gap-4">
{categories.map(({ label, Icon }) => {
return (
<CategoriesCard
key={v4()}
category={category}
label={label}
Icon={Icon}
changeSelected={() => setCustomValue("category", label)}
/>
);
})}
</div>
<div className="grid grid-cols-2 justify-center items-center gap-6">
{variables.map((variable: colorPickerProps) => {
return (
<ColorPicker
key={v4()}
variable={variable}
removeVariableFromState={removeVariableFromState}
addInputToState={addInputToState}
isProductCreated={isCreated}
/>
);
})}
</div>
</form>
)}
</div>
);
}
我尝试使用Callbacks,Memo,Ref,一切,但我认为问题出在它的逻辑上。我删除了
addInputtoState
并正常工作,所以我认为它的根源是它里面的setItem,但我不明白为什么更改它会重新渲染 ColorPicker。
我认为这个问题有些混乱。重新渲染与“组件完全卸载并重新安装”不同。从您的代码来看,您似乎对后者有问题。
实际的重新渲染是完全正常的,并且是使用 React 的事实。看起来您已尝试阻止重新渲染,但实际上您遇到的是性质完全不同的错误。
问题是误用了传递给
key
的 ColorPicker
属性。
每次父级
AddMenu
重新渲染时,它会在状态更改时执行此操作(注意:必然,这是无法阻止的),您将通过 key
调用重新生成 v4()
。
如果某个键在渲染之间发生变化,则该组件的实例将被完全卸载并再次安装,从而丢失该过程中的所有状态。密钥必须是稳定的,而不是随机的。这意味着每次该组件执行重新渲染周期时,它们都是相同的。
通常,这可以通过具有唯一的
variables
或类似的 id
的每个条目来完成。在 objects/variables
文件中,您应该考虑添加这样的属性。每个条目的值必须是唯一的。
然后你可以使用它作为密钥:
{variables.map((variable: colorPickerProps) => {
return (
<ColorPicker
key={variable.id}
variable={variable}
removeVariableFromState={removeVariableFromState}
addInputToState={addInputToState}
isProductCreated={isCreated}
/>
);
})}
请注意,您不需要
React.memo
包装纸。此类技术用于在出现性能问题时减少重新渲染(根据重新渲染的严格定义)。但你的情况似乎不是这样的。