React noob here.
I’m trying to use a combination of Context, useState, useEffect, and sessionStorage to build a functioning global data store that persists on page refresh.
When a user logins into the app, I fetch an API and then setUserData when I receive the response.
That part works great, but I realized if a user ever "refreshes" their page that the App resets the userData const to null (and the page crashes as a result).
I studied some articles and videos on using a combination of useEffect and sessionStorage to effectively replace the userData with what is currently in sessionStorage, which seemed like a sensible approach to handle page refreshes. [Source: React Persist State to LocalStorage with useEffect by Ben Awad]
However, I can’t seem to get the useEffect piece to work. For some reason useEffect is only firing on the / route and not on the /dashboard route?
I would expect because the useEffect is in App.js that it runs every time any route is refreshed (thus retrieving the latest data from sessionStorage).
I added some console logging to App.js for when events are being fired and included those logs below.
What am I not understanding correctly about useEffect? Why does it only fire when the / route is refreshed and not when the page /dashboard is refreshed?
App.js
import { useState, createContext, useEffect } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import '../css/app.css';
import Login from './auth/login';
import Dashboard from './dashboard/dashboard';
export const AppContext = createContext(null);
function App() {
console.log("App Load")
const [userData, setUserData] = useState(null);
useEffect(() => {
console.log("Use effect ran");
const user = sessionStorage.getItem("user");
if (user) {
setUserData(JSON.parse(user));
console.log("Retreived session storage");
}
},[]);
return (
<AppContext.Provider value={ { userData, setUserData } }>
<BrowserRouter>
<div className="App">
<Routes>
<Route path="/" element={<Login />}></Route>
<Route path="/dashboard" element={<Dashboard />}></Route>
</Routes>
</div>
</BrowserRouter>
</AppContext.Provider>
);
}
export default App;
Dashboard component
import { useContext } from 'react'
import '../../css/app.css'
import { AppContext } from '../app';
import Nav from '../../components/primary-nav';
const Dashboard = () => {
const { userData } = useContext(AppContext);
return (
<div>
<Nav />
<div id='dashboard'>
<div id='title' className='mt-[38px] ml-[11%]'>
<div className='h2'>Good morning, { userData.user_info.first_name }!</div>
</div>
</div>
</div>
)
}
export default Dashboard;
I would have expected useEffect() fires when the Dashboard page is refreshed, but here are the logs respectively.
Logs: Default Route Loads
Logs: Dashboard Route Loads
Bonus points
Can you help me understand why App Load is being fired more than once (seems it fires 4 times?)
>Solution :
Issue
The useEffect hook, with empty dependency array, runs only once, and since it is outside the router it’s completely unrelated to any routing. Additionally, the useEffect hook runs at the end of the initial render, so the wrong initial state value is used on the initial render cycle.
Solution
Initialize the state directly from sessionStorage and use the useEffect hook to persist the local state as it changes.
Example:
function App() {
console.log("App Load")
const [userData, setUserData] = useState(() => {
const user = sessionStorage.getItem("user");
return JSON.parse(user) || null;
});
useEffect(() => {
console.log("userData updated, persist state");
sessionStorage.getItem("user", JSON.stringify(userData));
}, [userData]);
return (
...
);
}
As with any potentially null/undefined values, consumers necessarily should use a null-check/guard-clause when accessing this userData state.
Example:
const Dashboard = () => {
const { userData } = useContext(AppContext);
return (
<div>
<Nav />
<div id='dashboard'>
<div id='title' className='mt-[38px] ml-[11%]'>
{userData
? (
<div className='h2'>Good morning, { userData.user_info.first_name }!</div>
) : (
<div>No user data</div>
)
}
</div>
</div>
</div>
);
};
Can you help me understand why App Load is being fired more than once (seems it fires 4 times?)
The console.log("App Load") in App is in the main component body. This is an unintentional side-effect. If you want to log when the App component mounts then use a mounting useEffect hook.
Example:
useEffect(() => {
console.log("App Load");
}, []); // <-- empty dependency to run once on mount
The other logs are likely related to React’s StrictMode component. See specifically Detecting Unexpected Side-effects and Ensuring Reusable State. The React.StrictMode component intentionally double-invokes and double-mounts the component to help you detect logical issues in your code. This occurs only in non-production builds.

