Next Js组件的状态没有更新

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

我正在为网站上的管理面板编写一个页面。我有一个组件,可以从后端接收数据(例如产品)并将其传递到目录编辑器组件。例如,我添加了一个新产品,使请求无效,然后使用 console.log 我看到请求被重新执行,但新数据没有传输到 CatalogEditor。需要刷新页面,然后组件中就会显示新的数据

为什么会这样?

管理面板组件

import HomeEditor from "@/components/home-editor";
import AboutEditor from "@/components/about-editor";
import ContactEditor from "@/components/contact-editor";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import CatalogEditor from "@/components/catalog-editor";
import { Product } from "@/types/product";
import { useEffect, useState } from "react";

interface AdminRightPanelProps {
  selectedMenu: string;
}
const CUSTOM_URL = process.env.NEXT_PUBLIC_CUSTOM_URL || "";

export default function AdminRightPanel({
  selectedMenu,
}: AdminRightPanelProps) {
  const { data: homePageContent, isLoading: isHomePageLoading } =
    useQuery<HomePageContent>({
      queryKey: ["homePageContent"],
      queryFn: async () => {
        const response = await axios.get(`${CUSTOM_URL}api/content/home`);
        return response.data;
      },
    });
  const { data: aboutPageContent, isLoading: isAboutPageLoading } =
    useQuery<AboutPageContent>({
      queryKey: ["aboutPageContent"],
      queryFn: async () => {
        const response = await axios.get(`${CUSTOM_URL}api/content/about`);
        return response.data;
      },
    });
  const { data: contactPageContent, isLoading: isContactPageLoading } =
    useQuery<ContactPageContent>({
      queryKey: ["contactPageContent"],
      queryFn: async () => {
        const response = await axios.get(`${CUSTOM_URL}api/content/contact`);
        return response.data;
      },
    });
  const { data: products, isLoading: isProductsLoading } = useQuery<Product[]>({
    queryKey: ["products"],
    queryFn: async () => {
      const response = await axios.get(`${CUSTOM_URL}api/content/products`);
      return response.data;
    },
  });

  const [productItems, setProductItems] = useState<Product[]>([]);

  useEffect(() => {
    if (products) {
      setProductItems(products);
    }
  }, [products]);

  const isLoading =
    isHomePageLoading ||
    isAboutPageLoading ||
    isContactPageLoading ||
    isProductsLoading;

  if (isLoading) {
    return <div className="m-auto">Loading...</div>;
  }

  switch (selectedMenu) {
    case "home":
      return <HomeEditor content={homePageContent} />;
    case "catalog":
      return <CatalogEditor products={productItems} />;
    case "about":
      return <AboutEditor content={aboutPageContent} />;
    case "contact":
      return <ContactEditor content={contactPageContent} />;
  }
}

目录编辑器组件

import { useToast } from "@/hooks/use-toast";
import React, { useEffect, useRef, useState } from "react";
import Image from "next/image";
import { Product } from "@/types/product";
import { ImageDown } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useQueryClient } from "@tanstack/react-query";
import EditProductItem from "@/components/edit-product-item";

interface CatalogEditorProps {
  products: Product[];
}

const CUSTOM_URL = process.env.NEXT_PUBLIC_CUSTOM_URL || "";

export default function CatalogEditor({ products }: CatalogEditorProps) {
  const { toast } = useToast();
  const queryClient = useQueryClient();
  const inputFileRef = useRef<HTMLInputElement | null>(null);

  const [category, setCategory] = useState<string | null>(null);
  const [base64Image, setBase64Image] = useState<string | null>(null);
  const [isButtonsDisabled, setIsButtonsDisabled] = useState<boolean>(false);
  const [isSubmitDisabled, setIsSubmitDisabled] = useState<boolean>(false);

  useEffect(() => {
    setIsButtonsDisabled(base64Image === null || base64Image === "");
  }, [base64Image]);

  useEffect(() => {
    setIsSubmitDisabled(
      base64Image === null ||
        base64Image === "" ||
        category === null ||
        category === "",
    );
  }, [base64Image, category]);

  function handleFileUpload() {
    inputFileRef.current?.click();
  }

  function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0];
    if (file) {
      const reader = new FileReader();
      reader.onloadend = () => {
        setBase64Image(reader.result as string);
      };
      reader.readAsDataURL(file);
    }
  }

  async function onSubmit() {
    if (base64Image && category) {
      const product: Product = {
        id: 0,
        base64image: base64Image,
        category: category,
      };

      fetch(`${CUSTOM_URL}api/content/products`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(product),
      })
        .then((response) => {
          if (response.ok) {
            toast({
              title: "Дані оновлено!",
              description: "Зміни скоро будуть застосовані!",
            });
          } else {
            toast({
              title: "Помилка!",
              description: "Щось пішло не так :( Спробуйте пізніше!",
            });
          }
        })
        .catch((error) => {
          toast({
            title: "Помилка!",
            description: "Щось пішло не так :( Спробуйте пізніше!",
          });
        });

      setBase64Image(null);
      await queryClient.refetchQueries({ queryKey: ["products"] });
    }
  }

  return (
    <div className="w-full m-4">
      <div>
        <div className="flex w-full h-[240px] border border-[#18181B] p-4 rounded-lg my-2">
          <div
            className="flex flex-row min-w-[300px] cursor-pointer"
            onClick={handleFileUpload}
          >
            {base64Image ? (
              <div className="flex items-center justify-center h-[210px] w-full">
                <Image
                  className="col-span-1 max-h-[210px] object-contain"
                  width={300}
                  height={300}
                  src={base64Image}
                  alt="product"
                />
              </div>
            ) : (
              <div className="flex items-center justify-center h-[210px] w-[300px] border border-[#18181B]">
                <ImageDown /> <p className="ml-1">Завантажте товар</p>
              </div>
            )}
            <input
              ref={inputFileRef}
              id="picture"
              type="file"
              style={{ display: "none" }}
              onChange={handleFileChange}
            />
          </div>
          <div className="flex flex-col mx-4 w-full">
            <p className="text-lg mb-2 ">Категорія</p>
            <Select onValueChange={(value) => setCategory(value)}>
              <SelectTrigger className="w-full" disabled={isButtonsDisabled}>
                <SelectValue placeholder="Виберіть категорію" />
              </SelectTrigger>
              <SelectContent>
                <SelectGroup>
                  <SelectLabel>Категорії</SelectLabel>
                  <SelectItem value="sofa">Диван</SelectItem>
                  <SelectItem value="corner">Кут</SelectItem>
                  <SelectItem value="kit">Комплект</SelectItem>
                  <SelectItem value="ottoman">Тахта</SelectItem>
                  <SelectItem value="bed">Ліжко</SelectItem>
                </SelectGroup>
              </SelectContent>
            </Select>
            <Button
              className="mt-2 rounded-md font-[600] py-[20px] px-6 text-white bg-red-500 hover:bg-red-800 duration-300"
              onClick={() => setBase64Image("")}
              disabled={isButtonsDisabled}
            >
              Видалити продукт
            </Button>
          </div>
        </div>
        <Button
          className="mt-1 ml-1 rounded-[50px] font-[600] py-[20px] px-6 text-white bg-gray hover:bg-black duration-300"
          onClick={onSubmit}
          disabled={isSubmitDisabled}
        >
          Зберегти
        </Button>
      </div>

      <div className="flex justify-between my-4 text-lg mx-2">
        <p>Наявні продукти</p>
        <p>Кількість - {products.length}</p>
      </div>
      {products?.map((item, index) => (
        <EditProductItem key={index} item={item} />
      ))}
    </div>
  );
}
javascript reactjs next.js react-hooks
1个回答
0
投票

您遇到的问题是由于 React Query 处理缓存的方式造成的。添加新产品时,查询不会自动重新获取数据,除非明确指示这样做。以下是发生这种情况的原因以及解决方法。

问题

您正在通过 CatalogEditor 中的 API 请求添加产品,但是虽然更改成功,但 CatalogEditor 在页面刷新之前不会反映新数据。这是因为 React Query 缓存了产品查询的结果,并且需要被告知在突变后重新获取此数据。

解决方案

为了确保 CatalogEditor 在更新后拥有最新的产品数据,您应该在突变后使产品查询无效或重新获取。您已经在 onSubmit 函数中使用了 queryClient.refetchQueries(),但问题可能是由于 AdminRightPanel 中的 useEffect 缺少依赖项所致。

  • 在突变回调中重新获取查询:确保您在添加产品后使用 queryClient.invalidateQueries 或 queryClient.refetchQueries 重新获取产品数据。

  • 更新AdminRightPanel的状态:不使用useEffect将产品同步到productItems,而是直接使用产品状态。如果需要本地修改,请确保产品具有 queryKey 依赖项以触发更新。

调整

// CatalogEditor.js

async function onSubmit() {
  if (base64Image && category) {
    const product = { id: 0, base64image: base64Image, category: category };
    await fetch(`${CUSTOM_URL}api/content/products`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(product),
    });

    // Invalidate or refetch the products query after the mutation
    queryClient.invalidateQueries("products");
  }
}

// 管理右侧面板:

export default function AdminRightPanel({ selectedMenu }) {
  const { data: products } = useQuery(["products"], fetchProducts);

  switch (selectedMenu) {
    case "catalog":
      return <CatalogEditor products={products} />;
    // other cases...
  }
}

解释

使用invalidateQueries可确保缓存的数据在突变后立即刷新,因此CatalogEditor将收到更新的产品列表,而无需重新加载页面。此外,直接将产品作为 prop 传递可以使 AdminRightPanel 更好地响应后端数据的变化。

此方法应该可以解决问题并确保 CatalogEditor 在每次更新后显示最新数据。希望这有帮助!

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