1

I am getting the error "window is not defined" in nextJS project. Here isMobile is storing the value that window size is less than 767.98 or not to execute the open/close hamburger menu functionality. This code was working fine in ReactJS but not working in NextJS. Please help me to figure out this issue.

import Link from 'next/link';
import React, { useState, useEffect, useRef } from "react";


const Navbar = () => {


    const isMobile = window.innerWidth <= 767.98;

    const [isMenuOpen, setIsMenuOpen] = useState(!isMobile);
    const toggle = () => isMobile && setIsMenuOpen(!isMenuOpen);
    const ref = useRef()

    useEffect(() => {
        if (isMobile) {
          const checkIfClickedOutside = (e) => {
            if (!ref.current?.contains(e.target)) {
              setIsMenuOpen(false);
            }
          };
        
          document.addEventListener("mousedown", checkIfClickedOutside);
        
          return () => {
            // Cleanup the event listener
            document.removeEventListener("mousedown", checkIfClickedOutside);
          };
        }
      }, []);




    return (
        <>
            <header>

                <nav>
                    <div className="nav">

                        <div className="nav-brand">
                            <Link href="/" className="text-black"><a>Website</a></Link>
                        </div>
                        <div ref={ref}>
                            <div className="toggle-icon" onClick={toggle}>
                                <i id="toggle-button" className={isMenuOpen ? 'fas fa-times' : 'fas fa-bars'} />
                            </div>
                            {isMenuOpen && (
                                <div className={isMenuOpen ? "nav-menu visible" : "nav-menu"}>
                                    <ul className="main-menu">

                                        <li><Link href="/" onClick={toggle}><a>Home</a></Link></li>
                                        <li><Link href="/blog" onClick={toggle}><a>Blog</a></Link></li>
                                        <li className="drp">
                                            <p className="dropbtn">Find <i className="fa-solid fa-angle-down"></i></p>
                                            <ul className="dropdown-content">
                                                <li><Link href="/find/portable-keyboards" onClick={toggle}><a>Portable Keyboards</a></Link></li>
                                            </ul>
                                        </li>
                                     

                                    </ul>
                                
                                </div>
                            )}

                        </div>
                    </div>
                </nav>

            </header>

        </>
    )
}

export default Navbar;
Youssouf Oumar
  • 29,373
  • 11
  • 46
  • 65
PRASHANT VERMA
  • 115
  • 1
  • 2
  • 9

2 Answers2

4

Next.js is a server-side rendering framework which means the initial call to generate HTML from the server. At this point, window object, is only available on the client-side (not on the server-side).

To solve this problem, you need to check window object availability.

import Link from 'next/link';
import React, { useState, useEffect, useRef } from "react";


const Navbar = () => {
    
    const isMobile = typeof window !== "undefined" && window.innerWidth <= 767.98
    const [isMenuOpen, setIsMenuOpen] = useState(!isMobile);
    const toggle = () => isMobile && setIsMenuOpen(!isMenuOpen);
    const ref = useRef()

    useEffect(() => {
        
        if (isMobile) {
          const checkIfClickedOutside = (e) => {
            if (!ref.current?.contains(e.target)) {
              setIsMenuOpen(false);
            }
          };
        
          document.addEventListener("mousedown", checkIfClickedOutside);
        
          return () => {
            // Cleanup the event listener
            document.removeEventListener("mousedown", checkIfClickedOutside);
          };
        }
      }, []);




    return (
        <>
            <header>

                <nav>
                    <div className="nav">

                        <div className="nav-brand">
                            <Link href="/" className="text-black"><a>Website</a></Link>
                        </div>
                        <div ref={ref}>
                            <div className="toggle-icon" onClick={toggle}>
                                <i id="toggle-button" className={isMenuOpen ? 'fas fa-times' : 'fas fa-bars'} />
                            </div>
                            {isMenuOpen && (
                                <div className={isMenuOpen ? "nav-menu visible" : "nav-menu"}>
                                    <ul className="main-menu">

                                        <li><Link href="/" onClick={toggle}><a>Home</a></Link></li>
                                        <li><Link href="/blog" onClick={toggle}><a>Blog</a></Link></li>
                                        <li className="drp">
                                            <p className="dropbtn">Find <i className="fa-solid fa-angle-down"></i></p>
                                            <ul className="dropdown-content">
                                                <li><Link href="/find/portable-keyboards" onClick={toggle}><a>Portable Keyboards</a></Link></li>
                                            </ul>
                                        </li>
                                     

                                    </ul>
                                
                                </div>
                            )}

                        </div>
                    </div>
                </nav>

            </header>

        </>
    )
}

export default Navbar;

Another way you can fix it is you can move that window logic into useEffect (or componentDidMount on a class-based component)

import Link from 'next/link';
import React, { useState, useEffect, useRef } from "react";


const Navbar = () => {
    
    const [isMobile, setIsMobile] = useState(false); //the initial state depends on mobile-first or desktop-first strategy
    const [isMenuOpen, setIsMenuOpen] = useState(true);
    const toggle = () => isMobile && setIsMenuOpen(!isMenuOpen);
    const ref = useRef()

    useEffect(() => {
      setIsMobile(window.innerWidth <= 767.98)
      setIsMenuOpen(window.innerWidth > 767.98)
    }, [])

    useEffect(() => {
        
        if (isMobile) {
          const checkIfClickedOutside = (e) => {
            if (!ref.current?.contains(e.target)) {
              setIsMenuOpen(false);
            }
          };
        
          document.addEventListener("mousedown", checkIfClickedOutside);
        
          return () => {
            // Cleanup the event listener
            document.removeEventListener("mousedown", checkIfClickedOutside);
          };
        }
      }, [isMobile]);




    return (
        <>
            <header>

                <nav>
                    <div className="nav">

                        <div className="nav-brand">
                            <Link href="/" className="text-black"><a>Website</a></Link>
                        </div>
                        <div ref={ref}>
                            <div className="toggle-icon" onClick={toggle}>
                                <i id="toggle-button" className={isMenuOpen ? 'fas fa-times' : 'fas fa-bars'} />
                            </div>
                            {isMenuOpen && (
                                <div className={isMenuOpen ? "nav-menu visible" : "nav-menu"}>
                                    <ul className="main-menu">

                                        <li><Link href="/" onClick={toggle}><a>Home</a></Link></li>
                                        <li><Link href="/blog" onClick={toggle}><a>Blog</a></Link></li>
                                        <li className="drp">
                                            <p className="dropbtn">Find <i className="fa-solid fa-angle-down"></i></p>
                                            <ul className="dropdown-content">
                                                <li><Link href="/find/portable-keyboards" onClick={toggle}><a>Portable Keyboards</a></Link></li>
                                            </ul>
                                        </li>
                                     

                                    </ul>
                                
                                </div>
                            )}

                        </div>
                    </div>
                </nav>

            </header>

        </>
    )
}

export default Navbar;

Note that, with this solution, your UI may have some flickering due to isMobile state

Nick Vu
  • 14,512
  • 4
  • 21
  • 31
  • Sorry but this is not working..the hamburger menu is always open. – PRASHANT VERMA Mar 20 '22 at 18:07
  • Which solution did you try? If you try the 2nd approach, I modified my code to be like `setIsMenuOpen(window.innerWidth > 767.98)` in `useEffect`. That would help to update your state once you land on the client-side @PRASHANTVERMA – Nick Vu Mar 20 '22 at 18:21
  • Your 1st approach throws an error (window is not defined), Your 2nd approach works well when clicking on the toggle icon but the menu does not hide when clicking outside of the menu. – PRASHANT VERMA Mar 20 '22 at 18:25
  • Could you try this on the 1st approach? `typeof window !== "undefined"`. I updated my code too. I also usually use this way to verify window, I thought it didn't need for your case but seemingly I was wrong. @PRASHANTVERMA – Nick Vu Mar 20 '22 at 18:27
  • I know why the 2nd approach didn't work either. We need to have `isMobile` in the dependency list of `useEffect` having `checkIfClickedOutside`. Whenever `isMobile` is updated, that `useEffect` will be updated too @PRASHANTVERMA – Nick Vu Mar 20 '22 at 18:31
  • All things working fine except the toggle close menu when clicking on links of the menu. The menu should close when clicking on any navigation link. – PRASHANT VERMA Mar 21 '22 at 02:26
-1

You can try this on your parent component definition.

import dynamic from 'next/dynamic'

const Navbar = dynamic(() => import('./Navbar'), { ssr: false });

const Parent = () => {
  ...
  return (
     {(typeof window !== 'undefined') &&
     <Navbar/>
     }
     ...
     <Footer/>
  );
}
StarDev
  • 174
  • 8