help interleaving server/client comps in layout?

picture attached is more or less what's happening. before RSC, i'd just have a factory (using factory loosely here, but nonetheless) that takes in username as an arg and returns all the button comps with username passed down to each. Now, I can't do that because I can't call that factory function in my RSC. Best way to handle this? I'm trying to avoid passing username from layout -> toolbar -> button, and trying to avoid useRouter since it just feels needless, but open to anything of course
No description
14 Replies
ATOMowy_grzyb
ATOMowy_grzyb13mo ago
"depending on which page we are on" = conditional rendering of buttons based on the page, username is dynamic path segment? I'm not sure what "gets /username param" means
jack
jack13mo ago
yep, dynamic segment which is passed into params for layout is what I meant and yes, i'm referring to conditional rendering:
if on /[username] -> pass down [A, B], if on /[username]/edit -> pass down [C, D]
if on /[username] -> pass down [A, B], if on /[username]/edit -> pass down [C, D]
Bump in case anyone has an idea on this : )
ATOMowy_grzyb
ATOMowy_grzyb13mo ago
https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts
Layouts do not have access to the route segments below itself. To access all route segments, you can use useSelectedLayoutSegment or useSelectedLayoutSegments in a Client Component.
Routing: Pages and Layouts
Create your first page and shared layout with the App Router.
jack
jack13mo ago
I don’t think you understand my question really Im having no issues regarding layouts, url parameters (dynamic or not), how to do conditional rendering, etc. Im strictly interested in how, given the constraints I’ve provided, one would recommend interleaving client and server comps
ATOMowy_grzyb
ATOMowy_grzyb13mo ago
No I don't I guess. You can just use closure to create the buttons with username in layout and pass them to toolbar, something like this
"use client";

export default function Button({
username,
label,
}: {
username: string;
label: string;
}) {
return (
<button onClick={() => alert(username)}>
{username} {label}
</button>
);
}
"use client";

export default function Button({
username,
label,
}: {
username: string;
label: string;
}) {
return (
<button onClick={() => alert(username)}>
{username} {label}
</button>
);
}
export default function Toolbar({ buttons }: { buttons: React.ReactNode }) {
return <div className="the-toolbar">{buttons}</div>;
}
export default function Toolbar({ buttons }: { buttons: React.ReactNode }) {
return <div className="the-toolbar">{buttons}</div>;
}
import Button from "./ButtonU";
import Toolbar from "./Toolbar";

export default function MovieLayout({
children,
}: {
children: React.ReactNode;
}) {
const username = "foo";

const Buttons = function () {
return (
<>
{username === "foo" && (
<>
<Button username={username} label="A" />
<Button username={username} label="B" />
</>
)}
{username === "bar" && (
<>
<Button username={username} label="C" />
<Button username={username} label="D" />
</>
)}
</>
);
};
return (
<>
<Toolbar buttons={<Buttons />} />
{children}
</>
);
}
import Button from "./ButtonU";
import Toolbar from "./Toolbar";

export default function MovieLayout({
children,
}: {
children: React.ReactNode;
}) {
const username = "foo";

const Buttons = function () {
return (
<>
{username === "foo" && (
<>
<Button username={username} label="A" />
<Button username={username} label="B" />
</>
)}
{username === "bar" && (
<>
<Button username={username} label="C" />
<Button username={username} label="D" />
</>
)}
</>
);
};
return (
<>
<Toolbar buttons={<Buttons />} />
{children}
</>
);
}
jack
jack13mo ago
Ok I’ll take a peek at this later when on computer thank you. I was thinking of doing some sort of higher order component but couldn’t iron out the details, looks like that’s maybe what this is? Either way, thank you
ATOMowy_grzyb
ATOMowy_grzyb13mo ago
I don't know if that makes sense because I don't know what's the use case. If, for example, I needed the Toolbar to say what action each button should do and that it's the Toolbar that knows which buttons to show when (makes sense based on single responsibility), and considering that I cannot attach an onClick to the buttons from Toolbar directly, I'd pass a prop that tells the button what to do (say edit, save, whatever) then I'd just pass username={username} to Toolbar and a second prop that Toolbar would use to conditionally render the buttons, like isEditable
"use client";

export default function Button({
children,
username,
type,
}: {
children: React.ReactNode;
username: string;
type: string;
}) {
return (
<button onClick={() => alert(`${username} performed action ${type}`)}>
{children}
</button>
);
}
"use client";

export default function Button({
children,
username,
type,
}: {
children: React.ReactNode;
username: string;
type: string;
}) {
return (
<button onClick={() => alert(`${username} performed action ${type}`)}>
{children}
</button>
);
}
import Button from "./ButtonU";

export default function Toolbar({
username,
isEditable,
}: {
username: string;
isEditable: boolean;
}) {
return (
<div className="the-toolbar">
{isEditable && (
<>
<Button username={username} type="edit">
Edit
</Button>
<Button username={username} type="save">
Save
</Button>
</>
)}
<Button username={username} type="open">
Open
</Button>
<Button username={username} type="close">
Close
</Button>
</div>
);
}
import Button from "./ButtonU";

export default function Toolbar({
username,
isEditable,
}: {
username: string;
isEditable: boolean;
}) {
return (
<div className="the-toolbar">
{isEditable && (
<>
<Button username={username} type="edit">
Edit
</Button>
<Button username={username} type="save">
Save
</Button>
</>
)}
<Button username={username} type="open">
Open
</Button>
<Button username={username} type="close">
Close
</Button>
</div>
);
}
import Toolbar from "./Toolbar";

export default function MovieLayout({
children,
}: {
children: React.ReactNode;
}) {
const username = "foo";
const isEditable = false; // based on if it's /user/foo/edit or /user/foo

return (
<>
<Toolbar username={username} isEditable={isEditable} />
{children}
</>
);
}
import Toolbar from "./Toolbar";

export default function MovieLayout({
children,
}: {
children: React.ReactNode;
}) {
const username = "foo";
const isEditable = false; // based on if it's /user/foo/edit or /user/foo

return (
<>
<Toolbar username={username} isEditable={isEditable} />
{children}
</>
);
}
if you try hard enough you can call passing the username through Toolbar to buttons prop drilling, since toolbar doesn't care about it, but then again using anything sophisticated just to avoid this would be overengineered IMO in this case, it's just one level of components first example uses closure to avoid prop drilling the username, but doesn't allow Toolbar to control the buttons, so I don't think it's a good solution at all
jack
jack13mo ago
i was kind of hoping for something like
const Layout = (props) => {
const {
ButtonA, ButtonB, ButtonC, ButtonD
} = generateButtons(props.params.username)

const arrayOfButtons =
props.params.pathname === "/edit" ? // this part is super psuedo-code, don't remember if this is proper syntax for getting pathname
[ButtonA, ButtonB] :
[ButtonC, ButtonD]

return (
<Header />
<Toolbar items={arrayOfButtons}/>
)
}
const Layout = (props) => {
const {
ButtonA, ButtonB, ButtonC, ButtonD
} = generateButtons(props.params.username)

const arrayOfButtons =
props.params.pathname === "/edit" ? // this part is super psuedo-code, don't remember if this is proper syntax for getting pathname
[ButtonA, ButtonB] :
[ButtonC, ButtonD]

return (
<Header />
<Toolbar items={arrayOfButtons}/>
)
}
but i can't call the generateButtons fn() in the server comp goal was to keep toolbar very generic: a bar that renders some buttons that it's given but yea, i think maybe i'm needlessly avoiding passing username down 1 extra level
ATOMowy_grzyb
ATOMowy_grzyb13mo ago
in your example, who is making the buttons interactive? do the buttons themselves know what they need to do? is it the factory that is producing them with onClick attached already etc?
jack
jack13mo ago
Hadn’t determined that yet, but was gonna pass down a fn for click as props
ATOMowy_grzyb
ATOMowy_grzyb13mo ago
but then Toolbar needs to walk the list of buttons and modify them based on something to add that prop to each of them otherwise the layout needs to pass it one by one former seems like not the best solution, latter blurs the responsibility
jack
jack13mo ago
Oof yea fair points Actually, should just work at button level, only piece of data they depend on is username I’m honestly starting to wonder if I should just delegate all the responsibility to the button and grab the username from useRouter() I hate to bring in the hook for no reason when I could just pass down the username, but I have to say, isolating responsibility this way feels ergonomic
ATOMowy_grzyb
ATOMowy_grzyb13mo ago
it's a client component already anyway, so one more hook won't hurt (tm) it does feel kinda weird that you have this piece of information you need right there in t he layout or the page, but it's hard to actually use it sometimes I get that a lot and then I go and dig through docs and stuff because typically there's a new way of doing something I'm used to be doing in a certain way
jack
jack13mo ago
Yea it’s quite frustrating honestly Before rsc, I very rarely found myself deliberating on how to structure my components. I’d still be careful and think it through, but never have I had to treat it like a serious architectural decision over the course of a day like this
Want results from more Discord servers?
Add your server