Using a toggle button to selectively hide/display components
Unanswered
Netherland Dwarf posted this in #help-forum
Netherland DwarfOP
Hello, I'm trying to create a gallery/showcase page in Next.js that toggles between a gallery view and a post-style view. I have a button at the top of the page to toggle between the two (shadcn toggle component), but I'm not sure how to use this to hide components when I'm unable to use useState within a server component (the whole page)
What's the most efficient and optimal way to handle this sort of scenario?
Page code: (Trying to hide ArtworkCarousel using Viewtoggle)
What's the most efficient and optimal way to handle this sort of scenario?
Page code: (Trying to hide ArtworkCarousel using Viewtoggle)
import ArtworkCarousel from "~/components/ui/artwork-carousel";
import { PageTitle } from "~/components/ui/page-title";
import { ViewToggle } from "~/components/ui/view-toggle";
export default async function Page() {
return (
<>
<main className="flex min-h-screen flex-col space-y-4 p-4 dark:bg-transparent">
<div className="flex items-start justify-between">
<PageTitle />
<span>
Gallery View: <ViewToggle />
</span>
</div>
<div className="flex w-full justify-start align-top">
<h1 className="text-black dark:text-white">Creative stuff</h1>
</div>
<div className="flex w-full justify-center">
Indexed image goes here
</div>
<div className="flex w-full justify-center">
<ArtworkCarousel />
</div>
</main>
</>
);
}
16 Replies
You could try to use url parameters, i think using url parameters might be the only way (in this case), im not sure if my implementation of it is the most efficient but it works good enough as a demonstration:
ViewToggle.tsx (has to be a client component):
Page.tsx
ViewToggle.tsx (has to be a client component):
"use client"
import { useRouter, useSearchParams } from 'next/navigation';
const ViewToggle = () => {
const router = useRouter();
const searchParam = useSearchParams();
const toggleView = () => {
if (searchParam.get('view') === 'gallery') {
router.push("/");
} else {
router.push("?view=gallery");
}
};
return (
<button onClick={toggleView}>
{searchParam.get('view') === 'gallery' ? 'List' : 'Gallery'}
</button>
);
};
export default ViewToggle;
Page.tsx
import ArtworkCarousel from "~/components/ui/artwork-carousel";
import { PageTitle } from "~/components/ui/page-title";
import { ViewToggle } from "~/components/ui/view-toggle";
export default async function Page({ searchParams }: { searchParams: any }) {
const isGalleryView = searchParams?.view === 'gallery';
return (
<>
<main className="flex min-h-screen flex-col space-y-4 p-4 dark:bg-transparent">
<div className="flex items-start justify-between">
<PageTitle />
<span>
Gallery View: <ViewToggle />
</span>
</div>
<div className="flex w-full justify-start align-top">
<h1 className="text-black dark:text-white">Creative stuff</h1>
</div>
<div className="flex w-full justify-center">
Indexed image goes here
</div>
<div className={`${isGalleryView ? "hidden" : "flex"} w-full justify-center`}>
<ArtworkCarousel />
</div>
</main>
</>
);
}
English Angora
I’d make a client component with usestate and import the component to the page
Then depending on state you can choose to render whatever you want
Thats also a good solution but it would be more for the client to render, since all of the following would need to be rendered on the client (this would be one component):
<div className="flex items-start justify-between">
<PageTitle />
<span>
Gallery View: <ViewToggle />
</span>
</div>
<div className="flex w-full justify-start align-top">
<h1 className="text-black dark:text-white">Creative stuff</h1>
</div>
<div className="flex w-full justify-center">
Indexed image goes here
</div>
<div className="flex w-full justify-center">
<ArtworkCarousel />
</div>
English Angora
Yes true. It depends on the use case though becuase when the client presses the toggle the site will have to fetch the new page from the server just to hide something
With state the toggle will work instantely
Yea it really depends on the use case, i would personally make it a client component because of the fact that if you make it a server component it would refetch the page every time a state changes.
i found a different fix for this, you could use shallow routing. it won't trigger a page refresh or data fetching methods.
by using window.history.pushState instead of router.push you update the browsers history stack and re-call methods like useSearchParams instead of refreshing the whole page.
(https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#using-the-native-history-api)
ViewToggle.tsx
by using window.history.pushState instead of router.push you update the browsers history stack and re-call methods like useSearchParams instead of refreshing the whole page.
(https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#using-the-native-history-api)
ViewToggle.tsx
"use client"
import { useRouter, useSearchParams } from 'next/navigation';
const ViewToggle = () => {
const searchParam = useSearchParams();
const toggleView = () => {
if (searchParam.get('view') === 'gallery') {
window.history.pushState(null, '', "/");
} else {
window.history.pushState(null, '', "?view=gallery");
}
};
return (
<button onClick={toggleView}>
{searchParam.get('view') === 'gallery' ? 'List' : 'Gallery'}
</button>
);
};
export default ViewToggle;
@Netherland Dwarf the code above would be an efficient solution to your problem
Brown bear
Be careful if you ever need to add any additional query params as this will clear them on toggleView 🙂
Yea i know, the code above is a demonstration
Brown bear
I was clarifying it for the person you were providing the code for, just incase they weren't aware
ðŸ‘
Netherland DwarfOP
Thank you so much for the responses, incredibly helpful!
I was trying to avoid using url parameters if possible, so these alternative solutions are just what I needed
Never heard of the history api, good food for thought