Next.js 表单上的组件重新渲染

问题描述 投票:0回答:1

基本上我的问题是,每次我向颜色选择器输入添加值或添加图像时,它都会重新渲染,我已经坚持了好几天了。

// 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。

reactjs forms rendering next.js13
1个回答
0
投票

我认为这个问题有些混乱。重新渲染与“组件完全卸载并重新安装”不同。从您的代码来看,您似乎对后者有问题。

实际的重新渲染是完全正常的,并且是使用 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
包装纸。此类技术用于在出现性能问题时减少重新渲染(根据重新渲染的严格定义)。但你的情况似乎不是这样的。

© www.soinside.com 2019 - 2024. All rights reserved.