[英]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/client
的Data
类型是什么,我假设它看起来像一个包含三个的对象此函数中的参数:
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.