I have a Next JS app where I am using Layout feature in the _app.tsx
component. I am showing a sidebar in the layout which is getting populated from an api call(get request).
Now I have another api call (update request) on button click in Index.tsx
file. 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.
A solution is to update the
Home
component to contain a state representing thedatas
prop and any added data the user adds when using the shopping list. ThefilteringList
state can use this "fluctuating" state instead of using the prop passed in. The extra state would look something like this:Note: Anywhere you use the prop
datas
, you'd instead usefluctuatingList
.Then when updating the search, map using the
fluctuatingList
: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
'sData
type is, I am going to assume it looks like an object with the three parameters in this function:Thumbs me up if you think its a good answer!