You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React AuthContext无法将数据存入LocalStorage问题求助

MERN项目React Context认证LocalStorage写入失败问题

我在跟着Freecodecamp教程做MERN项目,用React Context实现认证功能,目前碰到以下问题:

  • 登录请求能正常发送到后端,但数据无法存入LocalStorage
  • 之前用其他方式实现认证时,首次登录userId为undefined,刷新页面后才正常,且无法将userId传给后端;更换React Context方案后,LocalStorage写入问题仍未解决

相关代码

App.js

import React, { useState } from "react";
import { Navigate, Router, Routes, Route } from "react-router-dom";

// Pages
import {
  Home,
  Dashboard,
  MembersPage,
  ProfilePage,
  CreatePost,
  OnePost,
} from "./Pages/index";

export const AuthContext = React.createContext();
const initialState = {
  token: null,
  userId: null,
  isAuthenticated: false,
};
const reducer = (state, action) => {
  switch (action.type) {
    case "LOGIN":
      localStorage.setItem("token", JSON.stringify(action.payload.token));
      localStorage.setItem("userId", JSON.stringify(action.payload.userId));
      localStorage.setItem(
        "firstName",
        JSON.stringify(action.payload.firstName)
      );
      localStorage.setItem("lastName", JSON.stringify(action.payload.lastName));
      localStorage.setItem("email", JSON.stringify(action.payload.email));
      localStorage.setItem(
        "isAuthenticated",
        JSON.stringify(action.payload.isAuthenticated)
      );
      return {
        ...state,
        token: action.payload.token,
        userId: action.payload.user,
        isAuthenticated: true,
      };
    case "LOGOUT":
      localStorage.clear();
      return {
        ...state,
        token: null,
        userId: null,
        isAuthenticated: false,
      };
    default:
      return state;
  }
};

function App() {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  return (
    <AuthContext.Provider
      value={{
        state,
        dispatch,
      }}
    >
      <Routes>
        <Route path="/" exact element={<Home />} />
        <Route path="/dashboard" exact element={<Dashboard />} />
        <Route path="/profile" exact element={<ProfilePage />} />
        <Route path="/members" exact element={<MembersPage />} />
        <Route path="/posts" exact element={<CreatePost />} />
        <Route path="/posts/:id" exact element={<OnePost />} />
        <Route path="*" element={<Navigate to="/" replace />} />
      </Routes>
    </AuthContext.Provider>
  );
}
export default App;

Login.jsx

const Login = () => {
  const navigate = useNavigate();
  const { dispatch } = React.useContext(AuthContext);
  const initialState = {
    email: "",
    password: "",
    isSubmitting: false,
    errorMessage: null,
  };
  const [data, setData] = React.useState(initialState);

  const handleInputChange = (event) => {
    setData({
      ...data,
      [event.target.name]: event.target.value,
    });
  };
  const handleLogin = (event) => {
    event.preventDefault();
    setData({
      ...data,
      isSubmitting: true,
      errorMessage: null,
    });
    axios({
      method: "post",
      headers: { "Access-Control-Allow-Origin": "*" },
      url: "http://localhost:8080/home/login",
      data: {
        email: data.email,
        password: data.password,
      },
    })
      .then((res) => {
        navigate("/dashboard");
        if (res.ok) {
          return res.json();
        }
        throw res;
      })
      .then((resJson) => {
        dispatch({
          type: "LOGIN",
          payload: resJson,
        });
      })
      .catch((error) => {
        console.log(error);
        setData({
          ...data,
          isSubmitting: false,
          errorMessage: error.message || error.statusText,
        });
      });
  };

  return (
          <Box
            component="form"
            onSubmit={handleLogin}
            noValidate
          >
            <label htmlFor="email">
              Email Address
              <input
                type="text"
                value={data.email}
                onChange={handleInputChange}
                name="email"
                id="email"
              />
            </label>
            <label htmlFor="password">
              Password
              <input
                type="password"
                value={data.password}
                onChange={handleInputChange}
                name="password"
                id="password"
              />
            </label>
            {data.errorMessage && (
              <span className="form-error">{data.errorMessage}</span>
            )}

            <button disabled={data.isSubmitting}>
              {data.isSubmitting ? "Loading..." : "Login"}
            </button>
            <Grid container></Grid>
          </Box>
  );
};

export default Login;

问题修复方案

1. 调整导航执行时机

当前Login组件在请求返回后立即跳转,导致后续dispatch可能因组件卸载而无法执行。将navigate移到dispatch之后:

.then((resJson) => {
  dispatch({
    type: "LOGIN",
    payload: resJson,
  });
  navigate("/dashboard"); // 移至dispatch完成后执行
})

2. 修正Reducer字段不匹配问题

LOGIN分支中,存入LocalStorage的是action.payload.userId,但返回状态时写的是action.payload.user,字段不统一,修改为:

return {
  ...state,
  token: action.payload.token,
  userId: action.payload.userId, // 改为与LocalStorage存入的字段一致
  isAuthenticated: true,
};

3. 初始化时从LocalStorage恢复状态

页面刷新后Context状态会重置为初始值,需在App组件初始化时读取LocalStorage数据:

function App() {
  // 从LocalStorage加载已有认证数据
  const initialStateFromStorage = {
    token: localStorage.getItem("token") ? JSON.parse(localStorage.getItem("token")) : null,
    userId: localStorage.getItem("userId") ? JSON.parse(localStorage.getItem("userId")) : null,
    firstName: localStorage.getItem("firstName") ? JSON.parse(localStorage.getItem("firstName")) : null,
    lastName: localStorage.getItem("lastName") ? JSON.parse(localStorage.getItem("lastName")) : null,
    email: localStorage.getItem("email") ? JSON.parse(localStorage.getItem("email")) : null,
    isAuthenticated: localStorage.getItem("isAuthenticated") ? JSON.parse(localStorage.getItem("isAuthenticated")) : false,
  };
  const [state, dispatch] = React.useReducer(reducer, initialStateFromStorage);

  // 其余代码保持不变
}

4. 移除无效的前端CORS头

前端请求中设置Access-Control-Allow-Origin是无效的,该Header由后端配置,删除该字段:

axios({
  method: "post",
  // 移除headers: { "Access-Control-Allow-Origin": "*" }
  url: "http://localhost:8080/home/login",
  data: {
    email: data.email,
    password: data.password,
  },
})

5. 验证后端返回字段

确认后端/home/login接口返回的JSON包含token、userId、firstName、lastName、email字段,无拼写错误(比如后端返回user_id而非userId时需对应调整)。


内容的提问来源于stack exchange,提问作者Fenrir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 22:50:26