Next.js Discord

Discord Forum

Accessing dynamic params from parent/sibling

Answered
Siamese posted this in #help-forum
Open in Discord
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
Answered by joulev
then in the layout you can do
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>
  );
}
View full answer

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?

 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
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
@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 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.
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);
}
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>
);
}
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
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
:checkk:
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.
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
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 :checkk:
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 🤔
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
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 mobile
yeah
not sure how i feel about it

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
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