A common use case I run across is having a button in a layout toggle something in the children. For example, you could have a notifications button in a persisted layout that opens a side pane in the main app.
Ideally, you would be able to pass an isNotificationsPaneOpen state down to the children and have them render the pane. However,the Next 13 beta docs say this isn't possible:
Passing data between a parent layout and its children is not possible. However, you can fetch the same data in a route more than once, and React will automatically dedupe the requests without affecting performance.
Persisting this toggle state in the backend seems like a major overkill and feels unnatural. The backend shouldn't have to know about the frontend opening or closing a notifications panel.
It seems to me that this is a common enough use case that people will run into this issue often.
How should one generally think about this?
Concrete example:
// in layout.tsx
export default function NavbarLayout({
children,
}: {
children: React.ReactNode,
}) {
const [isNotificationsPaneOpen, setIsNotificationsPaneOpen]= useState(false);
return (
<div>
<nav>
<button onClick={()=>setIsNotificationsPanelOpen((prevValue)=>!prevValue)}>Notifications</button>
</nav>
{/* How do I pass isNotificationsPaneOpen to children? */}
{children}
</div>
);
}
So that in the page:
// in page.tsx
interface PageProps {
isNotificationsPaneOpen: boolean;
}
function Page({isNotificationsPaneOpen}:PageProps):JSX.Element {
// render something conditionally with isNotificationsPaneOpen
...
}
Current solutions
If the component that depends on the layout's state remains the same across pages (like the notifications panel example), it makes sense to include it here. But other examples - like toggling light/dark mode in the layout - render different things on each page so this is not a general solution.
localstorage to pass dataMore of a hack but you could have the layout send data to an external data source (ideally within the browser) and then query that data source from the main app to determine if the panel should be open or not.
I think your question is the result of a misconception of what a layout is for within the app router (directory) of Next.js 13.
It's supposed to be just an "UI that is shared between multiple pages" of a route segment (page.js, loading.js, etc.).
And, as you mentioned, they say:
Passing data between a parent layout and its children is not possible. However, you can fetch the same data in a route more than once, and React will automatically dedupe the requests without affecting performance.
This is because a layout is not a global state provider. For this need consider using already known technics, for example, a context. Find below an example of sharing a theme:
// app/theme-provider.js
'use client';
import { createContext, useContext } from 'react';
const ThemeContext = createContext();
export function useThemContext(){
return useContext(ThemeContext);
}
export default function ThemeProvider({ children }) {
return (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
);
}
// app/layout.js
import ThemeProvider from './theme-provider';
export default function RootLayout({ children }) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
// app/component.js
"use client";
import { useThemContext } from "./theme-provider";
export default function Component() {
const theme = useThemContext();
// ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With