I would like to fetch data in my layout.tsx and then share across my entire app. In order to achieve that I am fetching the data in layout and pass it into the Provider.
layout.tsx
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const res = await fetch(
"https://65ca740b3b05d29307e05057.mockapi.io/productsserver",
{
cache: "no-cache",
next: {
tags: ["products"],
},
}
);
const products: Product[] = await res.json();
return (
<html lang="en">
<ProductStoreProvider products={products}>
<body className={inter.className}>{children}</body>
</ProductStoreProvider>
</html>
);
}
ProductProvider.tsx
"use client";
import { useState, createContext, useContext } from "react";
import { Product } from "@/actions/actions";
interface ProductState {
products: Product[];
}
const createStore = (products: ProductState) => ({
products,
});
const ProductContext = createContext<ReturnType<typeof createStore> | null>(
null
);
const ProductStoreProvider = ({
products,
children,
}: {
products: ProductState;
children: React.ReactNode;
}) => {
const [store] = useState(() => createStore(products));
console.log("Product Provider", store);
// { product: 'Shirt', price: '798.00', id: '9' },
// { product: 'Fish', price: '114.00', id: '10' },
// { product: 'Pants', price: '645.00', id: '11' },
return (
<ProductContext.Provider value={store}>{children}</ProductContext.Provider>
);
};
export const useProductStore = () => {
if (!ProductContext)
throw new Error(
"useProductStore must be used within a ProductStoreProvider"
);
return useContext(ProductContext)!;
};
export default ProductStoreProvider;
I added a sanity check console.log() in there which gives me the output I expect.
In the page.tsx which is a server component I fetch the data again - because that's apparently how to do this.
export default async function Home() {
const res = await fetch(
"https://65ca740b3b05d29307e05057.mockapi.io/productsserver",
{
cache: "no-cache",
next: {
tags: ["products"],
},
}
);
const products: Product[] = await res.json();
return (
<main className="">
<h1 className="text-3xl font-bold text-center">Server Products</h1>
<h2 className="font-bold p-5">List of Products</h2>
<div className="flex gap-5 flex-wrap">
{products.map((product) => (
<div key={product.id} className="p-5 shadow">
<p>{product.product}</p>
<p>{product.price}</p>
</div>
))}
</div>
<ClientOverview />
</main>
);
}
The problem is the client component:
"use client";
import { useProductStore } from "@/store/ProductStore";
function ClientOverview() {
const { products } = useProductStore();
console.log(products); // Empty Array []
return (
<div className="flex gap-5 flex-wrap">
<h1>Client Producuts</h1>
{products.map((product) => (
<div key={product.id} className="p-5 shadow">
<p>{product.product}</p>
<p>{product.price}</p>
</div>
))}
</div>
);
}
export default ClientOverview;
even though inside the provider it tells me that it fetched the data its not coming back from the store. How do I pass fresh data down to the client component?
Could be a couple of issues.
Perhaps if the client component(ClientOverview()) is loading first before the state is loaded, data could be empty in the state.
This could be the reason why
const { products } = useProductStore()is returning empty array.Can you implement this:
Now that you have a indicator to see if the state has been loaded:-
Try to figure out, if the state is loaded initially before the clientoverview() works.
Instead of fetching the api, set up the state manually to see if the clientcomponent is rendering!!