javascriptreactjsreact-hookslocal-storage

Vanilla React Navbar non-reactive upon updating local storage


I have this react App structure. Some components are hidden based on the content of a local storage.

import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import AppRoutes from "./Routes";
import NavBar from "./components/NavBar";

const App = () => {
  return (
    <Router>
      <NavBar />
      <AppRoutes />
    </Router>
  );
};

export default App;

All of my other react components are responsive except Navbar. What am I doing wrong? This is my NavBar code:

import { useEffect, useState } from "react";
function NavBar() {
  const [role, setRole] = useState("");
  
  useEffect(() => {
    setRole(localStorage.getItem("tempUserRole"));
  }, []);
    return (
        <header className="bg-white">
            <nav className="" aria-label="Global">
                {/* Links (Center-aligned) */}
                <div className="">
                    {role==='a' && <a href="/events" className="">a</a>}
                    {role==='b' && <a href="/events" className="">EveBnts</a>}
                </div>

            </nav>
        </header>
    );
}

export default NavBar;

The problem is, I have to refresh the page to see the changes take effect. How do I solve this issue?


Solution

  • Your useEffect dependency array is empty, so the hook will only run when the NavBar first renders (or after a refresh, of course).

    The naive solution is to just add localStorage.getItem("tempUserRole") to the dependency array. It will work, but it will throw a warning because React does not want a complex function in the array since that's very expensive to track changes on, unlike a variable.

    The more complex solution is to create an event listener on the window and update your state that way. You can bundle that logic in a custom hook. I'll include Maxim's answer here from another question:

    You can use custom hooks for that

    const profile = useProfileData();
    
    import { useEffect, useState } from "react";
    
    function getProfileData() {
      return JSON.parse(localStorage.getItem('profile'));
    }
    
    export default function useProfileData() {
      const [profile, setProfile] = useState(getProfileData());
    
      useEffect(() => {
        function handleChangeStorage() {
          setProfile(getProfileData());
        }
    
        window.addEventListener('storage', handleChangeStorage);
        return () => window.removeEventListener('storage', handleChangeStorage);
      }, []);
    
      return profile;
    }
    

    if you don't get why it's not catched see that question Storage event not firing

    in summary it's catching events on different pages