How would I pass dynamic {children} to a client component from a server component
Unanswered
Knopper gall posted this in #help-forum
Knopper gallOP
import { ToggleableButton } from "@/components/toggleable-button";
import BotManager from "@/lib/botManager";
const Bot = new BotManager();
async function callback() {
"use server";
return Bot.isRunning ? await Bot.stop() : await Bot.start();
}
export default function Home() {
return (
<main>
<ToggleableButton callback={callback} initialState={Bot.isRunning}>
{Bot.isRunning ? "Stop" : "Start"}
</ToggleableButton>
</main>
);
}ToggleableButton is a client component, Bot.isRunning is changing but not updating the text, how woluld I do this without making the whole component it's own separate component in another file?
30 Replies
Knopper gallOP
I figured I can revalidateTag but that seems overkill?
Knopper gallOP
This is how I've cheesed it
import { ToggleableButton } from "@/components/toggleable-button";
import BotManager from "@/lib/botManager";
import { unstable_cache as cache, revalidateTag } from "next/cache";
const Bot = new BotManager();
async function callback() {
"use server";
const status = Bot.isRunning ? await Bot.stop() : await Bot.start();
revalidateTag('btnText');
return status;
}
export default function Home() {
return (
<main>
<ToggleableButton callback={callback} initialState={Bot.isRunning}>
{ cache(async () => Bot.isRunning ? "Stop" : "Start", [], { tags: ['btnText'] })() }
</ToggleableButton>
</main>
);
}@Knopper gall This is how I've cheesed it
ts
import { ToggleableButton } from "@/components/toggleable-button";
import BotManager from "@/lib/botManager";
import { unstable_cache as cache, revalidateTag } from "next/cache";
const Bot = new BotManager();
async function callback() {
"use server";
const status = Bot.isRunning ? await Bot.stop() : await Bot.start();
revalidateTag('btnText');
return status;
}
export default function Home() {
return (
<main>
<ToggleableButton callback={callback} initialState={Bot.isRunning}>
{ cache(async () => Bot.isRunning ? "Stop" : "Start", [], { tags: ['btnText'] })() }
</ToggleableButton>
</main>
);
}
this is not a good practice. Please revert back to this: https://nextjs-forum.com/post/1277737214356357274#message-1277737214356357274
After that, you can manage the state also in your client component. So you click the button, exectued the callback (serverside) and also update the client side state. You can also await the result of your server action, to update your optimistic clientside state. Like that you can instantly update the button and also validate the boot up (in this case)
After that, you can manage the state also in your client component. So you click the button, exectued the callback (serverside) and also update the client side state. You can also await the result of your server action, to update your optimistic clientside state. Like that you can instantly update the button and also validate the boot up (in this case)
@Knopper gall solved?
@B33fb0n3 this is *not* a good practice. Please revert back to this: https://discord.com/channels/752553802359505017/1277737214356357274/1277737214356357274
After that, you can manage the state also in your client component. So you click the button, exectued the callback (serverside) and also update the client side state. You can also await the result of your server action, to update your optimistic clientside state. Like that you can instantly update the button and also validate the boot up (in this case)
Knopper gallOP
Not quite what I'm after, I don't want to hardcode this into the client component
{Bot.isRunning ? "Stop" : "Start"}I want to have
<Component>Button Text</Component> and for it to update the Button Text dynamically@Knopper gall Not quite what I'm after, I don't want to hardcode this into the client component
`{Bot.isRunning ? "Stop" : "Start"}`
you won't hardcode it in your clientcomponent when you follow these instructions: https://nextjs-forum.com/post/1277737214356357274#message-1277873760715673701
Did you follow them?
Did you follow them?
@B33fb0n3 you won't hardcode it in your clientcomponent when you follow these instructions: https://discord.com/channels/752553802359505017/1277737214356357274/1277873760715673701
Did you follow them?
Knopper gallOP
It's the updating after actioning the callback that I'm having the issue with
@Knopper gall It's the updating after actioning the callback that I'm having the issue with
can you clarify what specifically does not work? This might help you: https://stackoverflow.com/help/how-to-ask
@B33fb0n3 can you clarify what specifically does not work? This might help you: https://stackoverflow.com/help/how-to-ask
Knopper gallOP
I've already explained, I appreciate your help but there's no need for the passive aggressiveness 🙂
So my component is a client component, it updates colour based on useTransition of the server action callback
The issue I'm having is how to update the button text
So my component is a client component, it updates colour based on useTransition of the server action callback
The issue I'm having is how to update the button text
{children} passed from the server component to the child, without revalidating the cacheIf you need more information I'm happy to answer specifics
that I'm having the issue with
this is not very helpful for me... I don't know what to ask, to help you. That's why I asked, that you clarify where you specifically have issues so I can help you more.
I understood your initial problem and I will help you with that if I know where you are stuck
@B33fb0n3 > that I'm having the issue with
this is not very helpful for me... I don't know what to ask, to help you. That's why I asked, that you clarify where you specifically have issues so I can help you more.
I understood your initial problem and I will help you with that if I know where you are stuck
Knopper gallOP
so I'm back with my intitial code, how would I gracefully update the button text without hardcoding it into the client component?
I am passing the state it should be in the server component. Via {children} inside the client component tags
So it is updating but the cache is stale, revalidating the cache makes it update the DOM
but that's not graceful, is it
@Knopper gall so I'm back with my intitial code, how would I gracefully update the button text without hardcoding it into the client component?
yes, then you can manage the state in your client component. So you click the button (update the state instantly. Thought the update the button text will be updated), exectue the callback (server action).
await the result of your server action, to update your optimistic clientside state. Like that you can instantly update the button text and also validate that the boot up (in this case) was successful
await the result of your server action, to update your optimistic clientside state. Like that you can instantly update the button text and also validate that the boot up (in this case) was successful
@B33fb0n3 yes, then you can manage the state in your client component. So you click the button (update the state instantly. Thought the update the button text will be updated), exectue the callback (server action).
await the result of your server action, to update your optimistic clientside state. Like that you can instantly update the button text and also validate that the boot up (in this case) was successful
Knopper gallOP
I can still pass the button text in {children} though?
I might be misunderstanding sorry
@Knopper gall I can still pass the button text in {children} though?
yes you can and conditionally render the content
@B33fb0n3 yes you can and conditionally render the content
Knopper gallOP
I feel like I'm having a moment of being stupid ngl
This is my client component, so how would I make the text dynamic, but passed via children instead of as a prop?
"use client";
import { Button, ButtonProps } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useTransition, useState } from 'react';
export function ToggleableButton(
{
variants: buttonVariants = { on: "success", off: "destructive" },
icons: buttonIcons = { on: "play_arrow", off: "stop" },
initialState = false,
children: buttonText,
callback,
}: {
variants?: { on: ButtonProps["variant"], off: ButtonProps["variant"] },
icons?: { on: string, off: string },
initialState?: boolean,
children?: React.ReactNode,
callback?: () => any,
}) {
const [isButtonOn, setIsButtonOn] = useState(initialState);
const [isPending, startTransition] = useTransition();
const spanClassList = ["icon"];
if (buttonText) spanClassList.push("mr-1");
if (isPending) spanClassList.push("animate-spin");
const iconJSX = (
<span className={cn(spanClassList)}>
{isPending ? "autorenew" : isButtonOn ? buttonIcons.off : buttonIcons.on}
</span>
)
return (
<Button
variant={isButtonOn ? buttonVariants.off : buttonVariants.on}
onClick={() => startTransition(async () => { setIsButtonOn(callback && await callback()) })}
disabled={isPending}
>
{(isButtonOn ? buttonIcons.off : buttonIcons.on) && iconJSX}
{buttonText}
</Button>
)
}This is my client component, so how would I make the text dynamic, but passed via children instead of as a prop?
I get I need to store the button text as state
@Knopper gall I feel like I'm having a moment of being stupid ngl
ts
"use client";
import { Button, ButtonProps } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useTransition, useState } from 'react';
export function ToggleableButton(
{
variants: buttonVariants = { on: "success", off: "destructive" },
icons: buttonIcons = { on: "play_arrow", off: "stop" },
initialState = false,
children: buttonText,
callback,
}: {
variants?: { on: ButtonProps["variant"], off: ButtonProps["variant"] },
icons?: { on: string, off: string },
initialState?: boolean,
children?: React.ReactNode,
callback?: () => any,
}) {
const [isButtonOn, setIsButtonOn] = useState(initialState);
const [isPending, startTransition] = useTransition();
const spanClassList = ["icon"];
if (buttonText) spanClassList.push("mr-1");
if (isPending) spanClassList.push("animate-spin");
const iconJSX = (
<span className={cn(spanClassList)}>
{isPending ? "autorenew" : isButtonOn ? buttonIcons.off : buttonIcons.on}
</span>
)
return (
<Button
variant={isButtonOn ? buttonVariants.off : buttonVariants.on}
onClick={() => startTransition(async () => { setIsButtonOn(callback && await callback()) })}
disabled={isPending}
>
{(isButtonOn ? buttonIcons.off : buttonIcons.on) && iconJSX}
{buttonText}
</Button>
)
}
This is my client component, so how would I make the text dynamic, but passed via children instead of as a prop?
the useTransition-Hook does [not allow async](https://react.dev/reference/react/useTransition#react-doesnt-treat-my-state-update-as-a-transition) function:
Another thing to mention: for what do you need the transition here? There won't be any blocking when clicking a button.
So: create a function that: updates your clientside state (that handles what will be displayed as button text) and await the result of your server action. Then set the clientside state to the result of your server action (successfull or not)
The function you pass to startTransition must be synchronous.
Another thing to mention: for what do you need the transition here? There won't be any blocking when clicking a button.
So: create a function that: updates your clientside state (that handles what will be displayed as button text) and await the result of your server action. Then set the clientside state to the result of your server action (successfull or not)
@B33fb0n3 the useTransition-Hook does [not allow async](https://react.dev/reference/react/useTransition#react-doesnt-treat-my-state-update-as-a-transition) function:
> The function you pass to startTransition must be **synchronous**.
Another thing to mention: for what do you need the transition here? There won't be any blocking when clicking a button.
So: create a function that: updates your clientside state (that handles what will be displayed as button text) and await the result of your server action. Then set the clientside state to the result of your server action (successfull or not)
Knopper gallOP
the startTransition is to get the pending state to give the button the disabled property and the spinning loading icon
it's only async to allow await in the callback which is useless now I removed the dead code
I had code after setIsButtonOn() before
So ultimately, the button should have four states, which look like this
@B33fb0n3 the useTransition-Hook does [not allow async](https://react.dev/reference/react/useTransition#react-doesnt-treat-my-state-update-as-a-transition) function:
> The function you pass to startTransition must be **synchronous**.
Another thing to mention: for what do you need the transition here? There won't be any blocking when clicking a button.
So: create a function that: updates your clientside state (that handles what will be displayed as button text) and await the result of your server action. Then set the clientside state to the result of your server action (successfull or not)
Knopper gallOP
Yeah I get this part, but I'm passing two states into the children
Your solution requires me to hardcode the states into the client component useState() does it not?
{Bot.isRunning ? "Stop" : "Start"}Your solution requires me to hardcode the states into the client component useState() does it not?