Accessing dynamic params from parent/sibling
Answered
Siamese posted this in #help-forum
SiameseOP
Hey I know this isnt possible, but im asking whats the second best way? Should I render everything in the last child? And use no layouts?
I feel like this is a very common problem, how did you handle this??
this is my folder structure:
/app
/[chatId]
/layout.tsx <- rendering users chats would be nice if i could get the chatId here and hightlight the selected chat
I feel like this is a very common problem, how did you handle this??
this is my folder structure:
/app
/[chatId]
/layout.tsx <- rendering users chats would be nice if i could get the chatId here and hightlight the selected chat
Answered by joulev
then in the layout you can do
then
async function Layout({ children }) {
const chats = await getAllChats();
return (
<div>
<Sidebar>
{chats.map(chat => (
<SidebarChatWrapper id={chat.id} key={chat.id}>
{/* this SidebarChat can still be an async server component if you want. fetch user pfp there for example */}
<SidebarChat chat={chat} />
</SidebarChatWrapper>
)}
</Sidebar>
<Main>
{children}
</Main>
</div>
)
}then
SidebarChatWrapper is a client component, something like"use client";
function SidebarChatWrapper({ id, children }) {
const params = useParams();
const isActive = id === params.id;
return (
<div style={{ background: isActive ? "lightgray" : "white" }}>
{children}
</div>
);
}64 Replies
SiameseOP
Oh or do I useParams and only render the selected chat on client side?
@Siamese Hey I know this isnt possible, but im asking whats the second best way? Should I render everything in the last child? And use no layouts?
I feel like this is a very common problem, how did you handle this??
this is my folder structure:
/app
/[chatId]
/layout.tsx <- rendering users chats would be nice if i could get the chatId here and hightlight the selected chat
you want to render something like this right?
where the sidebar contains list of all chats and the main page contains the chat content?
CHATS | @johndoe
@janedoe |----------------------
@richdoe |
@jackdoe |
[@johndoe] | Chat history
@johnnydoe | with @johndoe
@janiedoe | here
@jimmydoe |
@babydoe |where the sidebar contains list of all chats and the main page contains the chat content?
then in the layout you can do
then
async function Layout({ children }) {
const chats = await getAllChats();
return (
<div>
<Sidebar>
{chats.map(chat => (
<SidebarChatWrapper id={chat.id} key={chat.id}>
{/* this SidebarChat can still be an async server component if you want. fetch user pfp there for example */}
<SidebarChat chat={chat} />
</SidebarChatWrapper>
)}
</Sidebar>
<Main>
{children}
</Main>
</div>
)
}then
SidebarChatWrapper is a client component, something like"use client";
function SidebarChatWrapper({ id, children }) {
const params = useParams();
const isActive = id === params.id;
return (
<div style={{ background: isActive ? "lightgray" : "white" }}>
{children}
</div>
);
}Answer
the chats are still rendered on the server. only the highlighting of the active chat part is handled interactively on the client side
@joulev then in the layout you can do
tsx
async function Layout({ children }) {
const chats = await getAllChats();
return (
<div>
<Sidebar>
{chats.map(chat => (
<SidebarChatWrapper id={chat.id} key={chat.id}>
{/* this SidebarChat can still be an async server component if you want. fetch user pfp there for example */}
<SidebarChat chat={chat} />
</SidebarChatWrapper>
)}
</Sidebar>
<Main>
{children}
</Main>
</div>
)
}
then `SidebarChatWrapper` is a client component, something like
tsx
"use client";
function SidebarChatWrapper({ id, children }) {
const params = useParams();
const isActive = id === params.id;
return (
<div style={{ background: isActive ? "lightgray" : "white" }}>
{children}
</div>
);
}
SiameseOP
Thanks a lot. Yeah thats exactly what i meant, i guess the best solution. It would be very useful if there was a way to access all params from any server component, is this being filtered? Or is it just how the app router works?
@Siamese Thanks a lot. Yeah thats exactly what i meant, i guess the best solution. It would be very useful if there was a way to access all params from any server component, is this being filtered? Or is it just how the app router works?
thats just how the app router works. in the example above, the
the things that are rerun are the pages and the client component side of the layout. in the example above, i use the client component side of the layout to render the active state.
Layout component wont be rerun when you switch to a different chat, so if it could access the pathname, the lack of a rerun makes the data stale when you navigate across the chat items in the sidebar.the things that are rerun are the pages and the client component side of the layout. in the example above, i use the client component side of the layout to render the active state.
@joulev thats just how the app router works. in the example above, the `Layout` component wont be rerun when you switch to a different chat, so if it could access the pathname, the lack of a rerun makes the data stale when you navigate across the chat items in the sidebar.
the things that are rerun are the pages and the client component side of the layout. in the example above, i use the client component side of the layout to render the active state.
SiameseOP
Thanks for an explanation, makes perfect sense. Thats why using things like headers() causes rerenders therefore losing the fundamentals of using layouts
even when you use headers(), the layout won't be rerendered, causing stale states. that's why it's just fundamentally impossible to get the pathname in the layout reliably. this is not a bug, just a limitation of the design of the router itself that cannot be fixed
sometimes you will need "layout" that need to actually rerun whenever you navigate. in that case simply importing the layout into the pages themselves (instead of using layout.tsx) and it will work (and you will get to read the pathname reliably since the limitation above is not applicable)
SiameseOP
Ahh, okay, makes sense.
Thanks for everything !!
SiameseOP
I found a pretty cool solution, thought i would share
So I use css variables to change what needs to be changed if the chat is selected, this allows me to have only one wrapper for anything like this, and allows the general styling of the elem being wrapped to remain inside of it
In the ItemWrapper it sets selected-item if the ids match, and this changes which values is the var therefore updating the .sidebar-item
:root {
--sidebar-item-bg: theme(colors.transparent);
}
.selected-item {
--sidebar-item-bg: theme(colors.primary.700);
}
.sidebar-item-background {
background-color: var(--sidebar-item);
}
So I use css variables to change what needs to be changed if the chat is selected, this allows me to have only one wrapper for anything like this, and allows the general styling of the elem being wrapped to remain inside of it
In the ItemWrapper it sets selected-item if the ids match, and this changes which values is the var therefore updating the .sidebar-item
:root {
--sidebar-item-bg: theme(colors.transparent);
}
.selected-item {
--sidebar-item-bg: theme(colors.primary.700);
}
.sidebar-item-background {
background-color: var(--sidebar-item);
}
export default function ItemSelectedWrapper(props: {
children: ReactNode;
id: string;
property: string;
}) {
const params = useParams();
const property = params[props.property];
return (
<div className={props.id === property ? "selected" : ""}>
{props.children}
</div>
);
}
children: ReactNode;
id: string;
property: string;
}) {
const params = useParams();
const property = params[props.property];
return (
<div className={props.id === property ? "selected" : ""}>
{props.children}
</div>
);
}
SiameseOP
Im not sure if there an even better way to achieve something like this, since having to create wrappers for individual things is messy
@joulev then in the layout you can do
tsx
async function Layout({ children }) {
const chats = await getAllChats();
return (
<div>
<Sidebar>
{chats.map(chat => (
<SidebarChatWrapper id={chat.id} key={chat.id}>
{/* this SidebarChat can still be an async server component if you want. fetch user pfp there for example */}
<SidebarChat chat={chat} />
</SidebarChatWrapper>
)}
</Sidebar>
<Main>
{children}
</Main>
</div>
)
}
then `SidebarChatWrapper` is a client component, something like
tsx
"use client";
function SidebarChatWrapper({ id, children }) {
const params = useParams();
const isActive = id === params.id;
return (
<div style={{ background: isActive ? "lightgray" : "white" }}>
{children}
</div>
);
}
Asian black bear
message so that it appears on my discord thread
im not losing this
im not losing this
I mean… it’s just a simple active state toggler based on usePathname/useParams. It’s not rocket science. You are too focused on parallel routes, you forgot to think whether parallel routes are even a good solution. Here it isn’t a good solution.
i mean tbh this is more of a work around
when you consider real nested routing e.g. Remix
but this is nice still and understandable

ty a ton
🫡
No I’m not convinced this is a workaround. This is how the app router is designed. But I’ll agree to disagree.
Active state toggling has always been a thing where client components are the solution.
@joulev Active state toggling has always been a thing where client components are *the* solution.
Asian black bear
That I understand
I meant the design of making "nested' routing
But all good
It's not confusing, that's the most important thing
Asian black bear
@joulev my only issue at the moment
we need to show full screen on mobile for chat list and chat detail
we need to show full screen on mobile for chat list and chat detail
one approach that will work is handling the widths with css only
and always having chat side bar there
if params, we do an early return
but still this is quite tricky because then entire chat list would have. to be client side
so to clarify: desktop all is good 

on mobile, we wanna show full screen for each
so if i get redirected to chat detail i still fetch all chats in the background 🤔
so if i get redirected to chat detail i still fetch all chats in the background 🤔
on chat detail, we can handle it with css, there is no issue there
this means i may wanna have two chat lists inside layout
let me write some code
export default async function Layout({ children }) {
const chats = await getAllChats();
return (
<div className="hidden md:flex">
<div className="w-1/3">
<ChatList />
</div>
<div className="w-2/3">{children}</div>
</div>
<ChatList className="md:hidden" shouldHideOnDetailPage />
<div className="md:hidden">
{children}
</div>
)
}something like this
the downside is still fetching all chats if we're on mobile
SSR, we have no way of knowing if we're on mobile or not
so we could fetch all chats, but simply hide it if params is there
if params there, we do tailwind conditional, we simply set it to hidden
- entire chat list on the client?
the nice part about fetching on the server as opposed to the client is the reduced latency
server <-> server is faster than client/server, considering the initial content comes with the data
yeah, doesnt feel right
the nice part about fetching on the server as opposed to the client is the reduced latency
server <-> server is faster than client/server, considering the initial content comes with the data
yeah, doesnt feel right
im a bit of a performance geek
but its literally just one additional request
i think for now ill go with the code option
we'll sadly have to fetch all chats on chat detail on mobile UI
i mean to be fair
this isnt even next.js
this is a fundamental limitation of ssr?
fundamental limitation of ssr. you cant fetch different things based on different device viewports
Asian black bear
import type { ReactNode } from 'react'
import { ChatList } from '@/features/v2-chats/components/ChatList'
import { getAllChats } from '@/features/v2-chats/queries/getConversations'
export default async function Layout({ children }: { children: ReactNode }) {
const chats = await getAllChats()
return (
<>
<div className="hidden bg-white md:flex">
<div className="w-1/3">
<ChatList chats={chats} />
</div>
<div className="w-2/3">{children}</div>
</div>
<ChatList
className="w-full md:hidden"
chats={chats}
shouldHideWhenChatDetailOpen
/>
<div className="md:hidden">{children}</div>
</>
)
}@joulev
I assume to know if params id exist in chatlist, imma need to turn the entire thing into a client component? 🤔
shouldHideWhenChatDetailOpen if true we'd do hidden on mobileyeah
not sure how i feel about it
but it is what it is
but it is what it is
one thing i dislike severely about this approach
you're gonna have to wait for all chats to be fetched before you show chat detail page
bc there will be cases where users get redirected to chat detail right away
Asian black bear
if we fetch inside chat list
so chat list wrapper and then fetch inside there
so chat list wrapper and then fetch inside there
then chat list inside there is a client component
that could work and lets us use suspense
but we'd have to fetch twice in that case