React-Router路由跳转时函数组件Ref为空问题求助
Hey Nick, let's break down what's causing that frustrating null ref error and fix it up!
What's Going Wrong Here?
When you click the <Link> to navigate to the login page, here's the sequence of events that leads to the error:
- React Router catches the click via its synthetic event system and starts unmounting the
Landingcomponent (and its childLandingMenu). - The native
clickevent on the body fires next—since native events bubble up after React's synthetic events finish processing. - By this point, the
LandingMenuis already in the process of unmounting, somenuRef.currentbecomesnull. When youronBodyClicklistener tries to callcontains()onnull, you get the error.
Quick Fix: Guard Against Null Ref
The simplest way to stop the error is to add a check for menuRef.current before calling contains(). Use optional chaining (?.) to make this clean and concise:
const onBodyClick = (event) => { // Only proceed if the ref exists AND the target is inside the menu if (menuRef.current?.contains(event.target)) { return; } setOpen(false); };
This skips the contains() call entirely if the ref is null, preventing the error.
More Robust Cleanup Logic
While the above fixes the immediate issue, we can also refine the useEffect to ensure the event listener is always cleaned up properly—even if the component unmounts mid-navigation:
useEffect(() => { const onBodyClick = (event) => { if (menuRef.current?.contains(event.target)) { return; } setOpen(false); }; // Add the listener only when the menu is open if (open) { document.body.addEventListener("click", onBodyClick); } // Always return a cleanup function, regardless of whether we added the listener return () => { document.body.removeEventListener("click", onBodyClick); }; }, [open]);
This ensures no leftover event handlers hang around if the component unmounts, keeping your app clean and performant.
Do You Need to Switch to a Class Component?
Absolutely not! Function components with useEffect are fully capable of handling this scenario. The problem wasn't missing cleanup on unmount (your original logic works when the menu closes normally)—it was just the timing of the native click event firing after the component starts unmounting.
Full Updated LandingMenu Code
Here's the complete fixed LandingMenu component with both improvements:
import React, { useState, useEffect, useRef } from "react"; import "../../styles/landing-menu.css"; const LandingMenu = ({ Title, Icon, children }) => { const [open, setOpen] = useState(false); const menuRef = useRef(); useEffect(() => { const onBodyClick = (event) => { // Guard against null ref to avoid errors if (menuRef.current?.contains(event.target)) { return; } setOpen(false); }; if (open) { document.body.addEventListener("click", onBodyClick); } // Ensure listener is always cleaned up return () => { document.body.removeEventListener("click", onBodyClick); }; }, [open]); return ( <nav className="landing-menu" ref={menuRef}> <div className="landing-menu-title-wrapper" onClick={() => setOpen(!open)} > <h3 className="landing-menu-title">{Title}</h3> <div className="landing-menu-title-icon">{Icon}</div> </div> <ul className={`dropdown ${open ? "visible" : "hidden"}`}> {children} </ul> </nav> ); }; export default LandingMenu;
These small tweaks will resolve the null ref error while keeping your dropdown's intended behavior intact—no need to overhaul your React Router setup or component structure!
内容的提问来源于stack exchange,提问作者NickC

