skip to Main Content

App.jsx:

import { BrowserRouter as Router, Route, Routes, Navigate  } from 'react-router-dom'
import Leaderboard from './pages/Leaderboard'
import About from './pages/About'
import Scoresfeed from './pages/Scores-feed'
import Header from './components/Header/Header'
import './App.css'

const App = () => {
  return (
    <Router>
      <Header/>
      <Routes>
          <Route path="/about" element={<About />} />
          <Route path="/scores-feed" element={<Scoresfeed />} />
          <Route path="/" element={<Navigate to="/leaderboard" />} />
          <Route path="/leaderboard" element={<Leaderboard />} />
      </Routes>
    </Router>
  );
};

export default App;

Header.jsx:

import { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import './Header.css';

const Header = () => {
    const [isNavOpen, setIsNavOpen] = useState(false);
    const navRef = useRef(null);

    useEffect(() => {
        const handleOutsideClick = (event) => {
            if (navRef.current && !navRef.current.contains(event.target)) {
                setIsNavOpen(false);
            }
        };

        document.addEventListener('click', handleOutsideClick);

        return () => {
            document.removeEventListener('click', handleOutsideClick);
        };
    }, [isNavOpen]);

    const handleNavToggle = () => {
        setIsNavOpen((isNavOpen) => !isNavOpen);
    };

    return (
        <header className="header">
            <div className="nav-toggle" onClick={handleNavToggle}>
                <svg
                    width="24"
                    height="24"
                    xmlns="http://www.w3.org/2000/svg"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    strokeWidth="2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    className={`feather feather-chevron-down ${isNavOpen ? 'open' : ''}`}
                >
                    <polyline points="6 9 12 15 18 9"></polyline>
                </svg>
                <span className={`nav-toggle-label ${isNavOpen ? 'hidden' : ''}`}>Open navigation</span>
            </div>
            <nav ref={navRef} className={`nav ${isNavOpen ? 'open' : ''}`}>
                <ul className="nav-list">
                    <li className="nav-item">
                        <Link to="/leaderboard">
                            <svg
                                width="140"
                                height="50"
                                xmlns="http://www.w3.org/2000/svg"
                                viewBox="0 0 200 50"
                                preserveAspectRatio="xMidYMid meet"
                                className="svg-icon"
                            >
                                <rect x="5" y="40" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <rect x="180" y="5" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <line x1="1" y1="12" x2="12" y2="1" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <line x1="179" y1="49" x2="190" y2="37" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <polygon points="20,0 190,0 190,30 172,50 0,50 0,40 0,20" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="3" className="svg-border" />
                                <text className='text' x="50%" y="50%" textAnchor="middle" fill="white" dy=".3em" fontSize="30">Leaderboard</text>
                            </svg>
                        </Link>
                    </li>
                    <li className="nav-item">
                        <Link to="/scores-feed">
                            <svg
                                width="140"
                                height="50"
                                xmlns="http://www.w3.org/2000/svg"
                                viewBox="0 0 200 50"
                                preserveAspectRatio="xMidYMid meet"
                                className="svg-icon"
                            >
                                <rect x="5" y="40" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <rect x="180" y="5" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <line x1="1" y1="12" x2="12" y2="1" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <line x1="179" y1="49" x2="190" y2="37" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <polygon points="20,0 190,0 190,30 172,50 0,50 0,40 0,20" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="3" className="svg-border" />
                                <text className='text' x="50%" y="50%" textAnchor="middle" fill="white" dy=".3em" fontSize="30">Scores feed</text>
                            </svg>
                        </Link>
                    </li>
                    <li className="nav-item">
                        <Link to="/about">
                            <svg
                                width="140"
                                height="50"
                                xmlns="http://www.w3.org/2000/svg"
                                viewBox="0 0 200 50"
                                preserveAspectRatio="xMidYMid meet"
                                className="svg-icon"
                            >
                                <rect x="5" y="40" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <rect x="180" y="5" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <line x1="1" y1="12" x2="12" y2="1" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <line x1="179" y1="49" x2="190" y2="37" stroke="rgb(19, 65, 150)" strokeWidth="2" />
                                <polygon points="20,0 190,0 190,30 172,50 0,50 0,40 0,20" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="3" className="svg-border" />
                                <text className='text' x="50%" y="50%" textAnchor="middle" fill="white" dy=".3em" fontSize="30">About</text>
                            </svg>
                        </Link>
                    </li>
                </ul>
            </nav>
        </header>
    );
};

export default Header;

I have a component that needs to render a header. Inside it there is a svg “button” that should be toggled, thereby changing the state and adding/removing classes so that the menu is shown. But for some reason this doesn’t happen.

But if I change the state directly through React DevTools, then the menu appears:

State: false

State: true

I tried changing the dependencies in useEffect but it didn’t help. Classes are simply not added, and the state does not change. Please tell me what is the reason for my mistake

2

Answers


  1. Chosen as BEST ANSWER

    Аfter 12 hours I solved the problem like this:

    const [isNavOpen, setIsNavOpen] = useState(false);
    const navRef = useRef(null);
    
    useEffect(() => {
        const handleOutsideClick = (event) => {
            if (navRef.current && !navRef.current.contains(event.target)) {
                console.log('Clicked outside, closing navigation');
                setIsNavOpen(false);
            }
        };
    
        document.addEventListener('click', handleOutsideClick);
    
        return () => {
            document.removeEventListener('click', handleOutsideClick);
        };
    }, []);
    
    const handleNavToggle = (event) => {
        event.stopPropagation();
        console.log('Toggling navigation, current state:', isNavOpen);
        setIsNavOpen((prev) => {
            console.log('New state:', !prev);
            return !prev;
        });
    };
    
    console.log('Rendering component, isNavOpen:', isNavOpen);
    

  2. that looks like you’re going to be stacking eventlisteners anytime isNavOpen is updated you’ll add another listener, probably just removing isNavOpen from the dependency array could help, you only want to bind the eventlistener once on mount

    useEffect(() => {
        const handleOutsideClick = (event) => {
            if (navRef.current && !navRef.current.contains(event.target)) {
                setIsNavOpen(false);
            }
        };
    
        document.addEventListener('click', handleOutsideClick);
    
        return () => {
            document.removeEventListener('click', handleOutsideClick);
        };
    // just bind the event listen once on mount
    }, []);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search