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

用户注册后token未存入localStorage导致无法自动登录问题

注册后Token未存入localStorage导致无法自动跳转问题

注册表单提交后,用户已成功创建,但token未被存入localStorage,无法自动跳转到应用内部,必须手动登录。测试确认registerUser动作已获取到有效token(Postman调用/me端点验证正确),但注册完成后loadUser动作被拒绝,localStorage为空。

相关代码

RegisterForm.jsx

import React, { useState, useEffect} from 'react'
import { useNavigate } from 'react-router-dom';
import { useSelector, useDispatch  } from 'react-redux';
import { registerUser } from '../../slices/authSlice';
import FormGroup from '../UI/FormGroup';
import '../../styles/UI/FormGroup.css'

function RegisterForm() {
  const dispatch = useDispatch();
  const { isAuthenticated, error } = useSelector(state => state.auth)
  const navigate = useNavigate();

  const [formData, setFormData] = useState({
    fullname: "",
    birth_date: "",
    gender: "",
    email: "",
    password: "",
    password2: "",
  });
        
  const onChange = (e) => {
    const { name, value, type, checked } = e.target;
    if (type === "checkbox") {
      if (checked) {
        setFormData({ ...formData, [name]: [...gender, value] });
      } else {
        setFormData({
          ...formData,
          [name]: gender.filter((option) => option !== value),
        });
      }
    } else {
      setFormData({ ...formData, [name]: value });
    }
  }

  const formFields = [
    {
      name: "fullname",
      type: "text",
      placeholder: "Insert Full Name",
      label: "Full Name",
      value: formData.fullname,
      onChange: onChange
    },
    {
      name: "gender",
      type: "radio",
      placeholder: "",
      label: "",
      value: formData.gender,
      onChange: onChange,
      options: ["male", "female"] 
    },
    {
      name: "birth_date",
      type: "date",
      placeholder: "",
      label: "Date of Birth",
      value: formData.birth_date,
      onChange: onChange
    },
    {
      name: "email",
      type: "email",
      placeholder: "Email",
      label: "Email",
      value: formData.email,
      onChange: onChange
    },
    {
      name: "password",
      type: "password",
      placeholder: "Password",
      label: "Password",
      value: formData.password,
      onChange: onChange
    },
    {
      name: "password2",
      type: "password",
      placeholder: "Confirm Password",
      label: "Confirm Password",
      value: formData.password2,
      onChange: onChange
    }
  ]

  const onSubmitHandler = (e) => {
    e.preventDefault();
    dispatch(registerUser(formData));
  };

  useEffect(() => {
    if (isAuthenticated) {
      navigate('/');
    }
  }, [isAuthenticated, navigate]);

  return (
    <form action="" className="form_container" onSubmit={(e) => onSubmitHandler(e)}>
      {formFields.map((field) => (
        <FormGroup key={field.name} {...field} />
      ))}
      <button type="submit">Register</button>
      {error && <p style={{ color: 'red', marginTop: '10px' }} className="error">{error.message}</p>}
    </form>
  )
}

export default RegisterForm

authSlice.js

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
import setAuthToken from '../utils/setAuthToken';

const initialState = {
  access_token: localStorage.getItem('token'),
  isAuthenticated: false,
  user: {},
  isLoading: false,
  error: null,
};

export const loadUser = createAsyncThunk(
  'auth/loadUser',
  async (_, { rejectWithValue }) => {
    if (localStorage.token) {
      setAuthToken(localStorage.token);
    }

    try {
      const res = await axios.get('http://localhost:8000/api/auth/me');
      return res.data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

export const registerUser = createAsyncThunk(
  'auth/registerUser',
  async (user, { dispatch, rejectWithValue }) => {
    try {
      const config = {
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
        },
      };
      const body = JSON.stringify(user);
      const res = await axios.post(
        'http://localhost:8000/api/auth/register',
        body,
        config
      );
      // 先将token存入localStorage,再调用loadUser
      localStorage.setItem('token', res.data.access_token);
      dispatch(loadUser());
      return res.data;
    } catch (error) {
      return rejectWithValue(error.response.data.message);
    }
  }
);

export const loginUser = createAsyncThunk(
  'auth/loginUser',
  async ({ email, password }, { dispatch, rejectWithValue }) => {
    const body = { email, password };
    const config = {
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
      },
    };
    try {
      const res = await axios.post(
        'http://localhost:8000/api/auth/login',
        body,
        config
      );
      localStorage.setItem('token', res.data.access_token);
      dispatch(loadUser());
      return res.data;
    } catch (error) {
      return rejectWithValue(error.response.data.message);
    }
  }
);

export const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    logoutUser: (state) => {
      state.access_token = null;
      localStorage.removeItem('token');
      state.isAuthenticated = false;
      state.user = null;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(loadUser.pending, (state) => {
        state.isLoading = true;
        state.isAuthenticated = state.access_token ? true : false;
      })
      .addCase(loadUser.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isAuthenticated = true;
        state.user = action.payload;
        state.error = null;
      })
      .addCase(loadUser.rejected, (state, action) => {
        state.isLoading = false;
        state.isAuthenticated = false;
        state.error = action.payload;
      })
      .addCase(registerUser.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(registerUser.fulfilled, (state, action) => {
        // 直接赋值token,不要用localStorage.setItem的返回值(返回undefined)
        state.access_token = action.payload.access_token;
        state.isLoading = false;
        state.isAuthenticated = true;
        state.user = action.payload;
        state.error = null;
      })
      .addCase(registerUser.rejected, (state, action) => {
        state.isLoading = false;
        state.user = null;
        state.isAuthenticated = false;
        state.error = action.payload;
      })
      .addCase(loginUser.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(loginUser.fulfilled, (state, action) => {
        state.access_token = action.payload.access_token;
        state.isLoading = false;
        state.isAuthenticated = true;
        console.log(state.isAuthenticated);
        state.user = action.payload;
        state.error = null;
      })
      .addCase(loginUser.rejected, (state, action) => {
        state.isLoading = false;
        state.user = null;
        state.isAuthenticated = false;
        state.error = action.payload;
      });
  },
});

export const { logoutUser } = authSlice.actions;

export default authSlice.reducer;

App.jsx

import './App.css';
import Home from './components/UI/Home';
import { Route, Routes } from 'react-router-dom';
import Navbar from './components/UI/Navbar';
import Footer from './components/UI/Footer';
import Register from './Pages/Register';
import Login from './Pages/Login';
import Profile from './Pages/Profile';
import Dashboard from './Pages/Dashboard';
import PrivateRoute from './components/Routing/PrivateRoute';
import useAuth from './customHooks/useAuth';

function App() {
  const { isAuthenticated, loading } = useAuth();

  if (loading) {
    return <h1 className='title loader'>Loading...</h1>;
  }

  return (
    <>
      <div className='content-container'>
        <Navbar />
        <Routes>
          <Route path='/login' element={<Login />} />
          <Route path='/profile' element={<PrivateRoute><Profile /></PrivateRoute>} />
          <Route path='/register' element={<Register />} />
          <Route path="/" element={isAuthenticated ? <Dashboard /> : <Home />} />
        </Routes>
        <Footer />
      </div>
    </>
  );
}

export default App;

useAuth.js

import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loadUser } from '../slices/authSlice';

const useAuth = () => {
  const { isAuthenticated } = useSelector((state) => state.auth);
  const dispatch = useDispatch();
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const loadAuthenticatedUser = async () => {
      // loadUser不需要传参,内部会从localStorage读取
      await dispatch(loadUser());
      setLoading(false);
    };

    loadAuthenticatedUser();
  }, [dispatch]);

  return { isAuthenticated, loading };
};

export default useAuth;

问题分析与修复方案

核心问题1:Token存入时机错误

原registerUser thunk中,先调用dispatch(loadUser(res.data))再存token到localStorage,导致loadUser执行时localStorage无token,setAuthToken无法设置请求头,/api/auth/me请求失败。

修复:在registerUser thunk中,拿到后端返回的token后,先执行localStorage.setItem('token', res.data.access_token),再调用dispatch(loadUser()),确保loadUser执行时localStorage已有token。

核心问题2:state.access_token赋值错误

原registerUser.fulfilled中,用state.access_token = localStorage.setItem(...),但localStorage.setItem返回undefined,导致state.access_token被设为无效值。

修复:直接将state.access_token赋值为action.payload.access_token,存token的操作已在thunk中完成。

次要问题:loadUser参数冗余

loadUser thunk未使用传入的参数,useAuth中无需传参,直接调用dispatch(loadUser())即可。

修复后流程

  1. 用户提交注册表单,registerUser thunk发起请求
  2. 后端返回用户信息和token,thunk将token存入localStorage
  3. 调用loadUser,此时localStorage已有token,setAuthToken正确设置请求头
  4. loadUser成功获取用户信息,更新redux状态,isAuthenticated变为true
  5. RegisterForm中的useEffect监听isAuthenticated变化,触发跳转至首页

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 19:54:57