amannn / next-intl

🌐 Internationalization (i18n) for Next.js
https://next-intl-docs.vercel.app
MIT License
2.34k stars 209 forks source link

to remove the async/await from the root layout because i want to make my rootlayout a client component #545

Closed temtechie closed 11 months ago

temtechie commented 11 months ago

Is your feature request related to a problem? Please describe.

Error: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding 'use client' to a module that was originally written for the server. I get this error from nextjs because async/await is not yet supported in client component using the app Route.

now i want to use the 'use client' flag in my Root Layout to enable me use useState and other react hook.

Describe the solution you'd like

import {NextIntlClientProvider} from 'next-intl';
import {notFound} from 'next/navigation';

export function generateStaticParams() {
  return [{locale: 'en'}, {locale: 'de'}];
}

export default async function LocaleLayout({children, params: {locale}}) {
  let messages;
  try {
    messages = (await import(`../../messages/${locale}.json`)).default;
  } catch (error) {
    notFound();
  }

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

to remove the async/await . I have made my root layout a client component, and i can not use async/await in client component

Describe alternatives you've considered

to remove the async/await and have alternative to fetch

let messages; try { messages = (await import(../../messages/${locale}.json)).default; } catch (error) { notFound(); }

I have tried using .then it didnot work

HakkaOfDev commented 11 months ago

You can split the provider inside a client component named Providers than will load all your client providers without render the layout as client by default.

But if I'm not saying wrong, the NextIntlClientProvider already have the use client directive so you don't need to pass anything in your layout.

And your root layout must be server side and never use react states or what ever inside, only state manager as redux/zustand/jotai or react context itself that are component that will use use client directive inside but not directly from the layout.

temtechie commented 11 months ago

@HakkaOfDev which provider exactly,

here is my code from my layout.tsx can you send use this code to create the Provider?

`"use client";
import Header from "../component/Header";
import Sidebar from "../component/Sidebar";
import Loader from "../component/common/Loader";
import "./globals.css";
import { useState, useEffect, ReactNode } from "react";

import { NextIntlClientProvider } from 'next-intl';
import { notFound } from 'next/navigation';

type Props = {
  children: ReactNode;
  params: { locale: string };
};

export default async function RootLayout({
  children, params: { locale }
}: Props) {

  let messages;

  try {
    messages = ( await import(`../../messages/${locale}.json`)).default;
  } catch (error) {
    notFound();
  }

  const [sidebarOpen, setSidebarOpen] = useState(false);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    setTimeout(() => setLoading(false), 500);
  }, []);

  return (
    <html lang={locale}>
      <body suppressHydrationWarning={true}>
        <NextIntlClientProvider locale={locale} messages={messages}>
          <div className="">
            {loading ? (
              <Loader />
            ) : (
              <div className="flex h-screen overflow-hidden">
                <Sidebar
                  sidebarOpen={sidebarOpen}
                  setSidebarOpen={setSidebarOpen}
                />
                <div className="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
                  <Header
                    sidebarOpen={sidebarOpen}
                    setSidebarOpen={setSidebarOpen}
                  />
                  <main>
                    <div className="mx-auto w-full px-4 py-4 md:px-6 2xl:px-11">
                      {children}
                    </div>
                  </main>
                </div>
              </div>
            )}
          </div>
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

`

show me exactly what to do here

HakkaOfDev commented 11 months ago

For example here, you need to use a StateManager lib or a context on top level of your application that will store states for you because you can't use a layout as client. And you should move for example the the sidebarOpened state directly inside the Sidebar component.

I would use a simple wrapper component that include the loading thing in "use client" + the children so:

"use client";
import * from "react";

function LoadingWrapper({ children }) {
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    setTimeout(() => setLoading(false), 500);
  }, []);

  if(loading) return <Loader />;

  return children;
}

so your Layout can become:

import Header from "../component/Header";
import Sidebar from "../component/Sidebar";
import Loader from "../component/common/Loader";
import "./globals.css";
import { ReactNode } from "react";

import { NextIntlClientProvider } from 'next-intl';
import { notFound } from 'next/navigation';

type Props = {
  children: ReactNode;
  params: { locale: string };
};

export default async function RootLayout({
  children, params: { locale }
}: Props) {
  let messages;

  try {
    messages = ( await import(`../../messages/${locale}.json`)).default;
  } catch (error) {
    notFound();
  }

  return (
    <html lang={locale}>
      <body suppressHydrationWarning={true}>
        <NextIntlClientProvider locale={locale} messages={messages}>
          <LoadingWrapper>
              <div className="flex h-screen overflow-hidden">
                <Sidebar /> {/* states inside the component */}
                <div className="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
                  <Header />
                  <main>
                    <div className="mx-auto w-full px-4 py-4 md:px-6 2xl:px-11">
                      {children}
                    </div>
                  </main>
                </div>
              </div>
           </LoadingWrapper>
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

If you need to access to the sidebar state, you need probably to use a react context or a State Manager on top level of your application. I recommend zustand, it's simple.

temtechie commented 11 months ago

very helpful thanks man. i will send a dm on your email

HakkaOfDev commented 11 months ago

very helpful thanks man. i will send a dm on your email

You can add me via discord: hakkaofdev Have a good night!

temtechie commented 11 months ago

yeah. I will use redux. I am more familiar with redux

temtechie commented 11 months ago

is working now thanks man. i used your advice and did some twerking

amannn commented 11 months ago

Seems like this was resolved, will close the issue then.