2

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;

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

Comments

0

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/>
  );
}

1 Comment

It solved my issue where I am using Neo4j. Thanks.
0

You should access browser APIs like window only inside useEffect, event handlers.... That's because your code gets executed first on the server.

typeof window !== "undefined" is a hack that's not recommended, as it will most likely cause a hydration error.

You could refactor your code this way:

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

const Navbar = () => {
  let [isMobile, setIsMobile] = useState(null);
  const [isMenuOpen, setIsMenuOpen] = useState(false);
  const toggle = () => setIsMenuOpen(!isMenuOpen);
  const ref = useRef();
  useEffect(() => {
    setIsMobile(window.innerWidth <= 767.98);
  }, []);
  useEffect(() => {
    const checkIfClickedOutside = (e) => {
      if (!ref.current?.contains(e.target)) {
        setIsMenuOpen(false);
      }
    };

    const checkWindowWidth = () => {
      setIsMobile(window.innerWidth <= 767.98);
    };
    if (isMobile) {
      document.addEventListener("mousedown", checkIfClickedOutside);
    }

    window.addEventListener("resize", checkWindowWidth);

    return () => {
      // Cleanup the event listener
      window.removeEventListener("resize", checkWindowWidth);
      document.removeEventListener("mousedown", checkIfClickedOutside);
    };
  }, [isMobile]);

  if(!isMobil === null) return null;

  return <>...</>;
};

export default Navbar;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.