我有一个Next JS应用程序,我在_app.tsx
组件中使用布局功能。我在布局中显示了一个侧边栏,它正在从api调用(get请求)中填充。
现在我有另一个api调用(更新请求)按钮点击在Index.tsx
文件。这个更新请求改变了布局的侧边栏组件中的数据。
下面是我的代码片段
_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 } };
};
所以我的问题是如何更新布局的侧边栏数据后,数据从Index.tsx
文件更新。
一个解决方案是更新Home
组件,使其包含一个表示datas
道具的状态和用户在使用购物清单时添加的任何添加的数据。filteringList
状态可以使用这个"波动"。状态,而不是使用传入的prop。额外的状态看起来像这样:
注意:当你使用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();
...
然后在更新搜索时,使用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);
};
现在,我将诚实地说,我没有花时间消化您的代码片段来给出一个可用的解决方案,但是猜测@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);
};
如果你认为这是一个好答案,就给我点赞!