React - 在页面刷新时保留数据(useEffect、useContext、useState)

Par*_*ker 2 reactjs react-router react-context react-hooks use-effect

菜鸟在这里反应一下。

我正在尝试使用 Context、useState、useEffect 和 sessionStorage 的组合来构建一个在页面刷新时持续运行的功能全局数据存储。

当用户登录应用程序时,我会获取 API,然后setUserData当我收到响应时。

这部分效果很好,但我意识到,如果用户“刷新”他们的页面,应用程序会将userDataconst 重置为null(并且页面因此崩溃)。

我研究了一些关于使用 useEffect 和 sessionStorage 的组合来有效替换userDatasessionStorage 中当前内容的文章和视频,这似乎是处理页面刷新的明智方法。[来源: React Persist State to LocalStorage with useEffect by Ben Awad]

但是,我似乎无法让 useEffect 发挥作用。由于某种原因 useEffect 只在/路线上触发而不是在/dashboard路线上?

我希望因为 useEffect 位于 App.js 中,所以每次刷新任何路由时它都会运行(从而从 sessionStorage 检索最新数据)。

我在 App.js 中添加了一些控制台日志记录,以了解何时触发事件,并在下面包含这些日志。

我对 useEffect 的理解不正确吗?为什么它只在刷新路由时触发,而不是在刷新/页面时触发?/dashboard

应用程序.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;
Run Code Online (Sandbox Code Playgroud)

仪表板组件

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;
Run Code Online (Sandbox Code Playgroud)

我希望刷新仪表板页面时 useEffect() 会触发,但这里分别是日志。

日志:默认路由负载

登录组件加载

日志:仪表板路由负载

仪表板组件日志

奖励积分

您能帮我理解为什么 App Load 被多次触发(似乎触发了 4 次吗?)

Dre*_*ese 5

问题

useEffect钩子具有空的依赖数组,仅运行一次,并且由于它位于路由器之外,因此与任何路由完全无关。此外,该useEffect钩子在初始渲染结束时运行,因此在初始渲染周期中使用了错误的初始状态值。

解决方案

直接从 sessionStorage 初始化状态,并使用useEffect钩子在本地状态发生变化时保留它。

例子:

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 (
    ...
  );
}
Run Code Online (Sandbox Code Playgroud)

与任何潜在的空/未定义值一样,消费者在访问此状态时必须使用空检查/保护子句userData

例子:

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>
  );
};
Run Code Online (Sandbox Code Playgroud)

您能帮我理解为什么 App Load 被多次触发(似乎触发了 4 次吗?)

inconsole.log("App Load")位于App主要组件主体中。这是无意的副作用。如果您想在App安装组件时记录日志,请使用安装useEffect挂钩。

例子:

useEffect(() => {
  console.log("App Load");
}, []); // <-- empty dependency to run once on mount
Run Code Online (Sandbox Code Playgroud)

其他日志可能与 React 的StrictMode组件有关。具体请参阅检测意外副作用确保可重用状态。该React.StrictMode组件有意双重调用某些组件方法/挂钩/等并双重安装该组件,以帮助您检测代码中的逻辑问题。这种情况仅发生在非生产版本中。