Search code examples
javascriptreactjsnext.jsserver-side-rendering

Getting ReferenceError: localStorage is not defined even after adding "use client"


I have a simple app/components/organisms/Cookies.tsx modal window component that I import into app/page.tsx. I have added the 'use client' directive at the top of the component, but for some reason, I keep getting:

ReferenceError: localStorage is not defined

enter image description here

Code of my Cookies.tsx:

'use client';
import { useState } from 'react';

const Cookies = () => {
  const localStorageCookies = localStorage.getItem('cookies-accepted');
  const [cookiesAccepted, setCookiesStatus] = useState<boolean>(!!localStorageCookies);

  const acceptCookies = () => {
    localStorage.setItem('cookies-accepted', 'true');
    setCookiesStatus(true);
  }
  
  return (
    <>
      {!cookiesAccepted && (
        <section className="fixed max-w-md p-4 mx-auto bg-white border border-gray-200 left-12 bottom-16  rounded-2xl z-20">
          <h2 className="font-semibold text-gray-800 ">🍪 Cookie Notice</h2>

          <p className="mt-4 text-sm text-gray-600">
            We use cookies to ensure that we give you the best experience on our
            website.{' '}
            <a href="#" className="text-blue-500 hover:underline">
              Read cookies policies
            </a>
            .{' '}
          </p>

          <div className="flex items-center justify-between mt-4 gap-x-4 shrink-0">
            <button className="text-xs text-gray-800 underline transition-colors duration-300 hover:text-gray-600 focus:outline-none">
              Manage your preferences
            </button>

            <button
              className=" text-xs bg-gray-900 font-medium rounded-lg hover:bg-gray-700 text-white px-4 py-2.5 duration-300 transition-colors focus:outline-none"
              onClick={acceptCookies}
            >
              Accept
            </button>
          </div>
        </section>
      )}
    </>
  );
};

export default Cookies;

What am I doing wrong? I tried to put Cookies.tsx outside of app folder but it didn't help.

I'm using version Next.js version 13.4.1.


Solution

  • As you can read on rendering, actually, both Server and Client components render first on the server, and get sent as HTML to the browser (you can see the generated HTML file in the network tab of the browser). Here is a quote from the doc:

    To optimize the initial page load, Next.js will use React's APIs to render a static HTML preview on the server for both Client and Server Components. This means, when the user first visits your application, they will see the content of the page immediately, without having to wait for the client to download, parse, and execute the Client Component JavaScript bundle.

    The difference between the two is that when you add "use client", it means this component includes client interactivities: so Next.js should send the JavaScript needed (such as event handlers, effects, etc) and attach it to the previously displayed-as-plain-HTML component.

    So anything that's browser specific, such as localStorage, should not be called in a client component body, but only inside useEffect, event handlers...

    This should work in your case:

    "use client";
    
    import { useEffect, useState } from "react";
    
    const Cookies = () => {
      const [cookiesAccepted, setCookiesStatus] = useState<boolean | null>(null);
    
      const acceptCookies = () => {
        localStorage.setItem("cookies-accepted", "true");
        setCookiesStatus(true);
      };
    
      useEffect(() => {
        const localStorageCookies = localStorage.getItem("cookies-accepted");
        setCookiesStatus(!!localStorageCookies);
      }, []);
    
      if (cookiesAccepted === null || cookiesAccepted) {
        return null;
      }
    
      return (
        <section className="fixed max-w-md p-4 mx-auto bg-white border border-gray-200 left-12 bottom-16  rounded-2xl z-20">
          <h2 className="font-semibold text-gray-800 ">🍪 Cookie Notice</h2>
    
          <p className="mt-4 text-sm text-gray-600">
            We use cookies to ensure that we give you the best experience on our website.{" "}
            <a href="#" className="text-blue-500 hover:underline">
              Read cookies policies
            </a>
            .{" "}
          </p>
    
          <div className="flex items-center justify-between mt-4 gap-x-4 shrink-0">
            <button className="text-xs text-gray-800 underline transition-colors duration-300 hover:text-gray-600 focus:outline-none">
              Manage your preferences
            </button>
    
            <button
              className=" text-xs bg-gray-900 font-medium rounded-lg hover:bg-gray-700 text-white px-4 py-2.5 duration-300 transition-colors focus:outline-none"
              onClick={acceptCookies}
            >
              Accept
            </button>
          </div>
        </section>
      );
    };
    
    export default Cookies;
    

    And do not attempt the typeof window !== "undefined" hack as most likely, you would get a hydration error.