Home > Software design >  Window is not defined in nextJS
Window is not defined in nextJS

Time:03-21

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;

CodePudding user response:

Next.js is a server-side rendering framework which means the initial call to generate HTML from server. At this point, window object, which is only available on the client-side, is not available 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

CodePudding user response:

You have acces to window only inside useEffect in Next.js. That's because your code gets executed first by the server, and at this state, there is no browser object defined. You could refactor your code this way:


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


const Navbar = () => {
    let [isMobile, setIsMobile] = useState(false);
    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);     
        }else{
          setIsMenuOpen(true);
        }
       
        window.addEventListener("resize", checkWindowWidth);
 
        
        return () => {
          // Cleanup the event listener
          window.removeEventListener("resize", checkWindowWidth);
          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;

CodePudding user response:

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