Next.js Discord

Discord Forum

Using a toggle button to selectively hide/display components

Unanswered
Netherland Dwarf posted this in #help-forum
Open in Discord
Avatar
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)
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

Avatar
Pearls
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):
"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>
        </>
    );
}
Avatar
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
Avatar
Pearls
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>
Avatar
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
Avatar
Pearls
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.
Avatar
Pearls
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
"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
Avatar
Brown bear
Be careful if you ever need to add any additional query params as this will clear them on toggleView 🙂
Avatar
Pearls
Yea i know, the code above is a demonstration
Avatar
Brown bear
I was clarifying it for the person you were providing the code for, just incase they weren't aware
Avatar
Pearls
👍
Avatar
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