Sunday, 26 March 2023

Is it possible to prevent the children of a component from being imported in React? (Next.js 13)

I have a <Navbar> component that is a client component.
Here is the implementation:

Navbar.tsx

"use client";
import { usePathname } from "next/navigation";
import { useEffect, type FC } from "react";

import type { WithChildren } from "@types";

const Navbar: FC<WithChildren> = ({ children }) => {
  const pathname = usePathname();

  useEffect(() => {
    const onPageLoad = async () => {
      const { toast } = await import("react-hot-toast");

      toast.remove();
    };

    onPageLoad();
  }, [pathname]);

  if (pathname === "/auth") return null;

  return <nav className="flex h-16 items-center justify-between bg-light-gray p-2 dark:bg-black dark:text-white">{children}</nav>;
};

export default Navbar;

I am using it in the <RootLayout /> of my app:

app/layout.tsx

<main className={`${inter.className} dark:bg-dark-gray dark:text-white`}>
  <Toaster toastOptions= />
  <Navbar> {/* <---- Client Component */}
    <Logo /> {/* <---- Server Component */}
    <SearchBar /> {/* <---- Client Component */}
    <NavLinks /> {/* <---- Server Component */}
  </Navbar> {/* <---- Client Component */}
  <Provider>{children}</Provider>
</main>

In the Navbar component, I am returning null if on the auth page.
The problem is that the children are still present.

Here is what I mean:

In the <NavLinks /> component:

// ...
const UserIcon = lazy(() => import("@components/UserIcon"), { ssr: false });
// ...

const NavLinks: FC = () => (
  <section className="ml-auto flex">
    {/* ... */}
    <UserIcon /> {/* Client Component */}
  </section>
);

export default NavLinks;

The <UserIcon /> component is only rendered on the client. It gets a token from document.cookie, then it decodes the user object from the token using the jwt-token package, And then displays it in the UI (Just a simple Image Component)

Here is the component:

UserIcon.tsx

"use client";
import lazy from "next/dynamic";

import type { FC } from "react";

import useSession from "@hooks/useSession";

const Image = lazy(() => import("next/image"));
const Link = lazy(() => import("next/link"));

const UserIcon: FC = () => {
  const user = useSession();

  return (
    <Link href={`/people/${user.name}`}>
      <Image src={user.picture} alt={user.name} height={40} width={40} className="rounded-full" priority />
    </Link>
  );
};

export default UserIcon;

useSession.ts

import jwtDecode from "jwt-decode";

import type { FirebaseUser, SessionHook } from "@types";

import { getCookie } from "@utils/cookies";

/**
 * ! Only use in components that are not rendered on the server or in a `useEffect`
 */
const useSession: SessionHook = () => {
  const authToken = getCookie("auth_token");
  const user = jwtDecode<FirebaseUser>(authToken);

  return user;
};

export default useSession;

The problem is that, because the <Navbar /> is declared in the RootLayout and the JWT validation is done in one of it's children (<NavLinks />), it tries to get and decode the token even on the Authentication page. (And since the user is on the auth page, the user is not logged in and the token doesn't exist so jwt-decode throws an error.

Is it possible to prevent the children of the <Navbar> component to not perform any logic or just prevent them from being created?

I have tried making the root layout a client component and useState there, but that causes some hydration problems that I can't seem to fix.

Also making the root layout a client component increases the bundle size by 10kb+ and the navbar that is not shown on the Auth page also ships dead code since the user is not going to see it at all until they login.

NOTE: The useSession hook used to be a server-side function which got the cookies from next/headers.
But this had a downside, because it was used in the and the navbar is present on every page, AND using cookies forces dynamic rendering (SSR) which is not ideal since many pages are better suited to be statically generated at build time or regenerate at runtime (ISR) That is why I switched to a client-side implementation.



from Is it possible to prevent the children of a component from being imported in React? (Next.js 13)

No comments:

Post a Comment