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