Data sharing / Prop drilling #45543
Replies: 12 comments 45 replies
-
Edit 2024I still think that a little prop drilling isn't that bad, but I have been using React cache to share data during the SSR stage. The main benefit of using React cache is that the memoized result of the function call is scoped to the request. It it shown as a caveat but it actually works to our advantage. The downside is that this data is only live during the SSR pass. Once in the client you need to find ways to revalidate it, you can think of it as being set in stone, sort of... Here's a demo: The idea behind this technique boils down to this: import 'server-only';
import { cache } from 'react';
const requestContext = cache(() => {
return new Map<string, string>();
});
export const setRequestContext = (key: string, value: string) =>
requestContext().set(key, value);
export const getRequestContext = (key: string) => requestContext().get(key); First, taint the module so that it can only be imported by server side code. No Next, we cache a function that returns a Map. This means that, for this request:
Now we set the request context value, 'locale', at page level, and we can collect it from anywhere downstream. Just like one would with a React Context Provider. A caveat would be that if not set at page level, then we might sometimes read a non set context. Another caveat/benefit is that pages are rendered before layouts, so you can access the data you set on the page, at the parent layout. I tested this on local projects, firing requests with oha, and as expected the context correlates. There's a library: https://www.npmjs.com/package/server-only-context which does the same ~ Though perhaps it is best to understand what cache is really doing, and apply that to your app. There's a bit more of insight here as well: https://nextjs.org/docs/app/building-your-application/caching#request-memoization OriginalI am not sure if passing props one level down is prop-drilling. The common solution is to use composition: <A>
<B dataForB={data} />
<C>
<D {...dataforD} />
</C>
</A> That's always been the recommended action to avoid prop-drilling, even favoured over Context. https://www.youtube.com/watch?v=3XaXKiXtNjw - YouTube video by React Router DOM creator and maintainer Michael Jackson. The problem is that in Server + Client components, there might be combinations where props composition is not possible. Namely because a client component cannot just have a Server Component as its children, but rather needs to first use "composition", and then be assembled at a higher level.
This combination works: However I do think that you can compose your translations so that a client component can in turn make them available downstream to its children. Again, passing props down one level, hardly qualifies as prop-drilling, so I am squinting very hard here to try and see things from your perspective. |
Beta Was this translation helpful? Give feedback.
-
I am having the same problem, the only way I can think of is to put the data under some (edit: actually it is working. But as pointed out in the comments, it hurts traceability, bad in large projects) |
Beta Was this translation helpful? Give feedback.
-
nothing wrong with prop drilling my guy. React community needs to seriously drop this toxic |
Beta Was this translation helpful? Give feedback.
-
They intend for you to leverage react cache() and built-in fetch() caching with vercel data cache to refetch across components. |
Beta Was this translation helpful? Give feedback.
-
So in short I guess we are missing a way to get page params in any server component, right? I am having the same issue to understand how do this in proper way instead of passing it all the way down from Page component. |
Beta Was this translation helpful? Give feedback.
-
I didn't test myself, but according to docs, the way to go is |
Beta Was this translation helpful? Give feedback.
-
The problem with React cache is that you still need to call the cached function with the same arguments, which means you have to prop drill those arguments... Disclaimer: shameless self-promotion Usage is pretty simple: // mySharedData.ts
import { createCachedPromiseGetter } from 'rsc-better-cache';
interface MyData {
someProp: string;
}
export const myDataPromiseGetter = createCachedPromiseGetter<MyData>(); // MyReactServerComponent.tsx (React Server Component where the data is obtained)
import { myDataPromiseGetter } from './mySharedData';
const MyReactServerComponent = async (params) => {
const myData = await fetch(`some-url/${params.id}`);
myDataPromiseGetter().setPromiseResolution(myData);
// ...
} // MyOtherReactServerComponent.tsx (React Server Component lower (or higher !) in your app tree)
import { myDataPromiseGetter } from './mySharedData';
const MyOtherReactServerComponent = async () => {
const myData = await myDataPromiseGetter().promise;
// ...
} |
Beta Was this translation helpful? Give feedback.
-
Hey @nayaabkhan this question has been posed to Vercel countless times in the past year, by developers large and small. Literally the guy behind next-intl, the primary internationalization method, got radio silence. I asked them in person and got radio silence. It can be quite frustrating. It seems like the only way to do this as of now is using react’s cache. This is what the ecosystem decided to use “as an unstabile, tomporary solution” until something better shows up. This is what next-intl also ended up using. https://dev.to/jdgamble555/easy-context-in-react-server-components-rsc-1mdf |
Beta Was this translation helpful? Give feedback.
-
It's 2024 and we're still facing such basic issues, I'm shocked to see there isn't any proper solution for such a common issue |
Beta Was this translation helpful? Give feedback.
-
my use case here is Visual Editing and Previews with a CMS (Sanity). In a page as a server component, I query for a set of information that determines if some components render. Some of those components are server components that make requests themselves. In order to use visual editing or previews with these components using a "previewDrafts" perspective with the CMS (Sanity), we use a hook to query live data. It seems like for live editable previews, the current recommendations from sanity and vercel require all components that need props from server components to be client components. |
Beta Was this translation helpful? Give feedback.
-
Hi guys this is what i am trying to solve.I am using Next.js 14 with the app router. In my main layout file, I am fetching user data using a server component and then sharing that data with a client component. I have a context provider that passes this data to the context, allowing me to easily access the user data across all client components on different pages. However, I am facing an issue: while I can easily access the user data from the context in client components on all pages, I need to access this data in a few server components on other pages as well. How can I achieve this? Should I use cookies on the server side for this, or is there a better approach?
|
Beta Was this translation helpful? Give feedback.
-
I was able to share data from // server-ctx.ts
import { ServerContext } from "@/types"
import serverContext from "server-only-context"
interface ServerContext {
// some sync data
// some async data will be fetched in root layout
}
let _res: (ctx: ServerContext) => void
const promise = new Promise<ServerContext>((res) => (_res = res))
const ctx = serverContext<Promise<ServerContext>>(promise)
export const serverCtx = { get: ctx[0], set: _res! } // RootLayout.ts
const ctx = await getServerCtx()
serverCtx.set(ctx) // another page.ts (server component)
const ctx = await serverCtx.get() |
Beta Was this translation helpful? Give feedback.
-
The Context section clarifies many use cases for sharing data among Server and Client Components.
One case also needs to be included: Passing data down from components higher up in the tree down. For example, translations fetched on a
page
orlayout
based on a URL parameter such as[locale]
(see example):As the
dict
requireslang
parameter, it can only be gotten from apage
orlayout
but not in an intermediate or leaf component, such as:Neither can we use
context
to achieve this in a Server Component. The only possibility I see is prop drilling.Is there a recommendation for this kind of data sharing? Preferably something other than prop drilling?
Beta Was this translation helpful? Give feedback.
All reactions