简体   繁体   English

由于 Next JS 中其他同级组件中的数据更新而重新渲染一个同级组件

[英]Rerendering of one sibling component due to data update in other sibling component in Next JS

I have a Next JS app where I am using Layout feature in the _app.tsx component.我有一个 Next JS 应用程序,我在其中使用_app.tsx组件中的布局功能。 I am showing a sidebar in the layout which is getting populated from an api call(get request).我在布局中显示了一个侧边栏,该侧边栏是通过 api 调用(获取请求)填充的。

Now I have another api call (update request) on button click in Index.tsx file.现在我在Index.tsx文件中单击按钮时有另一个 api 调用(更新请求)。 This update request changes data in the layout's sidebar component.此更新请求更改布局侧边栏组件中的数据。

Here are my code snippets这是我的代码片段

_app.tsx

import { type AppType } from "next/app";
import { type Session } from "next-auth";
import { SessionProvider } from "next-auth/react";

import { trpc } from "../utils/trpc";

import "../styles/globals.css";
import MainLayout from "../components/layouts/MainLayout";

const MyApp: AppType<{ session: Session | null }> = ({
  Component,
  pageProps: { session, ...pageProps },
}) => {
  return (
    <SessionProvider session={session}>
      <MainLayout>
        <Component {...pageProps} />
      </MainLayout>
    </SessionProvider>
  );
};

export default trpc.withTRPC(MyApp);

MainLayout.tsx

import React, { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";

import { trpc } from "../../utils/trpc";

import { AiOutlineOrderedList } from "react-icons/ai";
import { BsCartCheckFill, BsGraphUp, BsSuitHeartFill } from "react-icons/bs";
import { IoRefresh } from "react-icons/io5";
import { IoMdArrowBack } from "react-icons/io";
import { MdModeEditOutline } from "react-icons/md";

import { Space_Grotesk } from "@next/font/google";

import { SelectedData } from "@prisma/client";

const spaceGrotesk = Space_Grotesk({
  weight: "500",
  subsets: ["latin"],
});

type MainLayoutProps = {
  children: React.ReactNode;
};

type DefaultItemProps = {
  setItemMode: React.Dispatch<React.SetStateAction<"default" | "add" | "show">>;
  data: SelectedData[]
};

type ItemProps = {
  setItemMode: React.Dispatch<React.SetStateAction<"default" | "add" | "show">>;
};

const MainLayout = ({ children }: MainLayoutProps) => {
  const [itemMode, setItemMode] = useState<"default" | "add" | "show">(
    "default"
  );
  const pathname = useRouter().pathname.split("/")[1];

  const { data: selectedData } = trpc?.selectedData?.getAllData?.useQuery();

  return (
    <div className={`flex h-screen flex-row gap-0 ${spaceGrotesk.className}`}>
      <div className="flex min-h-full w-20 flex-col items-center justify-between bg-white py-6">
        <div className="rounded-full bg-[#3f3d56] p-3">
          <BsSuitHeartFill size="1.3rem" className="text-[#f9a109]" />
        </div>

        <div className="flex flex-col gap-12">
          <Link href="/">
            <span
              className={`tooltip rounded-full p-3 ${
                pathname === "" && "text-[#f9a109]"
              }`}
              id="Items"
            >
              <span className="hidden">Items</span>
              <AiOutlineOrderedList size="1.3rem" />
            </span>
          </Link>
          <Link href="/history">
            <span
              className={`tooltip rounded-full p-3 ${
                pathname === "history" && "text-[#f9a109]"
              }`}
              id="History"
            >
              <span className="hidden">History</span>
              <IoRefresh size="1.3rem" />
            </span>
          </Link>
          <Link href="/statistics">
            <span
              className={`tooltip rounded-full p-3 ${
                pathname === "statistics" && "text-[#f9a109]"
              }`}
              id="Statistics"
            >
              <span className="hidden">Statistics</span>
              <BsGraphUp size="1.3rem" />
            </span>
          </Link>
        </div>

        <div className="relative rounded-full bg-[#f9a109] p-3">
          <span className="absolute top-[-6px] right-[-6px] flex h-5 w-5 items-center justify-center rounded-lg bg-[#eb5757] p-2 text-white">
            3
          </span>
          <BsCartCheckFill size="1.3rem" className="text-white" />
        </div>
      </div>

      {children}

      {itemMode === "default" && <DefaultItem setItemMode={setItemMode} data={selectedData!} />}
      {itemMode === "add" && <AddItem setItemMode={setItemMode} />}
      {itemMode === "show" && <ShowItem setItemMode={setItemMode} />}
    </div>
  );
};

export default MainLayout;

const DefaultItem = ({ setItemMode, data }: DefaultItemProps) => {
  return (
    <div className="flex w-[20%] flex-col justify-between bg-white">
      <div className="flex h-full flex-col items-center gap-5 bg-[#fff0de] py-6 px-5">
        <div className="flex items-center justify-center gap-7 rounded-lg bg-[#80485b] px-6">
          <img
            className="relative right-1 h-32 w-12 -translate-y-4 rotate-[-20deg]"
            src="/kissclipart-cola-bottle-png-clipart-fizzy-drinks-beer-bottle-853f3ea787dad24a-removebg-preview.png"
            alt="bear-bottle"
          />

          <div className="flex flex-col items-center justify-center gap-3">
            <span className="text-white">Didn't find what you need?</span>
            <button
              onClick={() => setItemMode("add")}
              className="w-fit rounded-xl bg-white px-4 py-2 text-[#80485b]"
            >
              Add Item
            </button>
          </div>
        </div>

        <div className="flex w-full items-center justify-between">
          <span className="text-xl">Shopping List</span>
          <MdModeEditOutline size="1.3rem" />
        </div>

        <div className="flex flex-col gap-5">
          <div className="flex max-h-[440px] flex-col gap-4 overflow-y-auto px-1">
            {data?.map((item) => (
              <div className="flex flex-col gap-2" key={item.id}>
                <span className="text-[14px] text-slate-500">
                  {item.name}
                </span>
                <div className="flex flex-col gap-1">
                  {item?.items.map((item: string, i: number) => (
                    <div key={i} className="flex items-center justify-between">
                      <span className="min-w-[179px] max-w-[180px] truncate">
                        {item}
                      </span>
                      <button className="rounded-lg border-2 border-[#f9a109] px-2 py-1 text-[#f9a109]">
                        3 pcs
                      </button>
                    </div>
                  ))}
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="my-5 flex items-center justify-center rounded-xl  border-2 border-[#f9a109]">
        <input
          className="w-full bg-transparent px-4 outline-none"
          placeholder="Enter a name"
          type="text"
          title="name"
        />
        <button className="rounded-lg bg-[#f9a109] px-5 py-3 text-white">
          Save
        </button>
      </div>
    </div>
  );
};

const AddItem = ({ setItemMode }: ItemProps) => {
  return (
    <div className="flex min-h-full w-[20%] flex-col justify-between gap-7 bg-white py-5 px-4">
      <div className="flex flex-col gap-5">
        <h3 className="text-2xl">Add a new item</h3>

        <div className="flex flex-col gap-5">
          <div className="flex flex-col gap-1">
            <label htmlFor="name">Name</label>
            <input
              className="rounded-lg border-2 border-stone-500 px-4 py-2 outline-none hover:border-[#f9a109] focus:border-[#f9a109]"
              placeholder="Enter a name"
              type="text"
              id="name"
            />
          </div>

          <div className="flex flex-col gap-1">
            <label htmlFor="note">Note (optional)</label>
            <textarea
              className="rounded-lg border-2 border-stone-500 px-4 py-2 outline-none hover:border-[#f9a109] focus:border-[#f9a109]"
              placeholder="Enter a note"
              rows={4}
              id="note"
            />
          </div>

          <div className="">
            <label htmlFor="images">Images</label>
            <input
              className="w-full rounded-lg border-2 border-stone-500 px-4 py-2 outline-none hover:border-[#f9a109] focus:border-[#f9a109]"
              placeholder="Enter an url"
              type="text"
              id="images"
            />
          </div>

          <div className="flex flex-col gap-1">
            <label htmlFor="category">Category</label>
            <input
              className="rounded-lg border-2 border-stone-500 px-4 py-2 outline-none hover:border-[#f9a109] focus:border-[#f9a109]"
              placeholder="Enter a category"
              type="text"
              id="category"
            />
          </div>
        </div>
      </div>

      <div className="flex items-center justify-between">
        <button
          onClick={() => setItemMode("default")}
          className="rounded-lg px-5 py-3"
        >
          Cancel
        </button>

        <button
          onClick={() => setItemMode("show")}
          className="rounded-lg bg-[#f9a109] px-5 py-3 text-white"
        >
          Save
        </button>
      </div>
    </div>
  );
};

const ShowItem = ({ setItemMode }: ItemProps) => {
  return (
    <div className="flex min-h-full w-[20%] flex-col justify-between gap-7 bg-white py-5 px-4">
      <div className="flex flex-col gap-5">
        <button className="flex items-center gap-2 px-3 text-[#f9a109]">
          <IoMdArrowBack size="1.3rem" /> back
        </button>

        <div className="flex flex-col gap-5">
          <div className="flex items-center justify-center">
            <img
              className="h-44 rounded-lg"
              src="/avocado-slices-500x500.jpg"
              alt="item-image"
            />
          </div>

          <div className="flex flex-col gap-1">
            <h6 className="text-gray-500">Name</h6>
            <span className="text-lg">Avocado</span>
          </div>

          <div className="flex flex-col gap-1">
            <h6 className="text-gray-500">Category</h6>
            <span className="text-lg">Fruits and Vegetable</span>
          </div>

          <div className="flex flex-col gap-1">
            <h6 className="text-gray-500">Note</h6>
            <span className="text-lg">
              Lorem ipsum dolor sit amet consectetur, adipisicing elit. Nihil
              soluta culpa, fugit aut, ea corrupti rem molestias dolor dolorem
              odit voluptate, facilis fuga reprehenderit commodi perspiciatis
              optio aperiam recusandae aliquam.
            </span>
          </div>
        </div>
      </div>

      <div className="flex items-center justify-between">
        <button
          onClick={() => setItemMode("add")}
          className="rounded-lg px-5 py-3"
        >
          Back
        </button>

        <button
          onClick={() => setItemMode("default")}
          className="rounded-lg bg-[#f9a109] px-5 py-3 text-white"
        >
          Save
        </button>
      </div>
    </div>
  );
};

Index.tsx

import { useState } from "react";
import { GetServerSideProps, type NextPage } from "next";
import Head from "next/head";
// import Link from "next/link";
// import { signIn, signOut, useSession } from "next-auth/react";

import { type Data, PrismaClient } from "@prisma/client";

import { Dancing_Script } from "@next/font/google";

import { IoAddOutline } from "react-icons/io5";
import { AiOutlineSearch } from "react-icons/ai";
import Overlay from "../components/common/Feedback/Overlay";

import { trpc } from "../utils/trpc";

type HomePageProps = {
  datas: Data[];
};

const dancingScript = Dancing_Script({
  weight: "700",
  subsets: ["latin"],
});

const Home: NextPage<HomePageProps> = ({ datas }) => {
  const [filteringList, setFilteringList] = useState(datas);
  const [search, setSearch] = useState("");
  const mutation = trpc.selectedData.addItemToShopingList.useMutation();

  const searchHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSearch(e.target.value);
    const filteredList = datas.map((list) => {
      return {
        ...list,
        items: list.items.filter((item) =>
          item.toLowerCase().includes(e.target.value.toLowerCase())
        ),
      };
    });
    setFilteringList(filteredList);
  };

  const addItemToShopingList = async (
    item: string,
    name: string,
    id: string
  ) => {
    if (mutation.error) {
      return alert("Something went wrong");
    }
    mutation.mutate({
      id,
      name,
      item,
    });
  };

  return (
    <>
      <Head>
        <title>Shoppingify | Items</title>
        <meta name="description" content="Generated by create-t3-app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <Overlay isShowing={false} />

      <div className="flex min-h-full flex-1 flex-col gap-12 bg-[#fafafe] px-16 py-8">
        <div className="flex w-full items-center gap-40">
          <h1 className="text-4xl">
            <span className={`${dancingScript.className} text-[#f9a109]`}>
              Shoppingify
            </span>{" "}
            allows you take your shopping list wherever you go
          </h1>

          <div className="relative flex items-start rounded-lg shadow-lg">
            <AiOutlineSearch className="absolute top-4 left-3" size="1.5rem" />
            <input
              className="rounded-lg px-12 py-4 outline-none"
              onChange={searchHandler}
              value={search}
              type="search"
              name="search"
              placeholder="Search Items"
              id="search"
            />
          </div>
        </div>

        <div className="flex max-h-[600px] flex-col gap-10 overflow-y-auto py-2">
          {filteringList?.map((list) => (
            <div key={list?.id} className="flex flex-col gap-7">
              <h3 className="text-2xl">{list?.name}</h3>
              <div className="flex flex-wrap items-center justify-center gap-5 space-x-3">
                {list?.items.map((item: string, idx: number) => (
                  <div
                    className="flex items-center justify-center gap-3 rounded-md px-4 py-2 shadow-md"
                    key={idx}
                  >
                    {item}{" "}
                    <button
                      onClick={() =>
                        addItemToShopingList(item, list?.name, list?.id)
                      }
                      title="add-item"
                    >
                      <IoAddOutline size="1.3rem" />
                    </button>
                  </div>
                ))}
              </div>
            </div>
          ))}
        </div>
      </div>
    </>
  );
};

export default Home;

export const getServerSideProps = async () => {
  const prisma = new PrismaClient();
  const datas = await prisma.data.findMany();

  return { props: { datas } };
};

So my question is how can I update the layout's sidebar data after data is updated from Index.tsx file.所以我的问题是如何在从Index.tsx文件更新数据后更新布局的侧边栏数据。

A solution is to update the Home component to contain a state representing the datas prop and any added data the user adds when using the shopping list.一种解决方案是更新Home组件以包含表示datas属性的状态以及用户在使用购物清单时添加的任何添加数据。 The filteringList state can use this "fluctuating" state instead of using the prop passed in. The extra state would look something like this: filteringList状态可以使用这个“波动”状态而不是使用传入的 prop。额外的状态看起来像这样:

Note : Anywhere you use the prop datas , you'd instead use fluctuatingList .注意:在任何使用道具datas的地方,您都应该使用fluctuatingList

const Home: NextPage<HomePageProps> = ({ datas }) => {
  const [fluctuatingList, setFluctuatingList] = useState(datas);
  // You can still set the filtered list to initially be set with `datas`
  const [filteringList, setFilteringList] = useState(datas);
  const [search, setSearch] = useState("");
  const mutation = trpc.selectedData.addItemToShopingList.useMutation();
...

Then when updating the search, map using the fluctuatingList :然后在更新搜索时,使用fluctuatingList进行映射:

  const searchHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSearch(e.target.value);
    // Check the line below
    const filteredList = fluctuatingList.map((list) => {
      return {
        ...list,
        items: list.items.filter((item) =>
          item.toLowerCase().includes(e.target.value.toLowerCase())
        ),
      };
    });
    setFilteringList(filteredList);
  };

Now, I'll be honest, I did not take the time to digest your snippets to give a usable solution, but guessing what @prisma/client 's Data type is, I am going to assume it looks like an object with the three parameters in this function:现在,老实说,我没有花时间消化您的代码片段来提供可用的解决方案,而是猜测@prisma/clientData类型是什么,我假设它看起来像一个包含三个的对象此函数中的参数:

  const addItemToShopingList = async (
    item: string,
    name: string,
    id: string
  ) => {
    if (mutation.error) {
      return alert("Something went wrong");
    }

    // Pulling this into a `const` for reuse
    const newData = {
      id,
      name,
      item,
    };

    // Here is where we mutate the data, I am guessing through some API call
    // We also want to set the fluctuating state here:
    setFluctuatingList((prevList) => ([ ...Array.from(prevList), newData ]));
    mutation.mutate(newData);
  };

Thumbs me up if you think its a good answer!如果您认为这是一个好的答案,请给我竖起大拇指!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM