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

React登录跳转带用户_id专属页时_id为undefined异常排查

React+React Router+Redux 登录带ID重定向问题修复

问题场景

基于React、React Router、Redux开发的前端应用,需要实现用户登录完成后自动重定向到携带用户自身_id的专属主页功能,核心实现代码如下:

根组件App.js代码

class App extends Component {
  componentDidMount() {
    store.dispatch(loadUser());
  }

  render() {
    return (
      <React.Fragment>
        <AppNavbar />
        <div className="App">
          <Switch>
            <Route path="/not-found" component={NotFound} />
            <AuthRoute path="/profile/settings" type="private">
              <Profile />
            </AuthRoute>
            <AuthRoute path="/setup/school" type="private">
              <SchoolDetails />
            </AuthRoute>
            <AuthRoute path="/setup/team" type="private">
              <TeamManagement />
            </AuthRoute>
            <AuthRoute path="/create/teams" type="private">
              <TeamSetup />
            </AuthRoute>
            <AuthRoute path="/register/setup" type="guest">
              <ProfileSetup />
            </AuthRoute>
            <AuthRoute path="/events" type="private">
              <SimpleSlider />
            </AuthRoute>
            <AuthRoute path="/register" type="guest">
              <RegisterModal />
            </AuthRoute>
            <AuthRoute path="/login" type="guest">
              <LoginModal />
            </AuthRoute>

            <AuthRoute exact path="/" type="private">
              <SimpleSlider />
            </AuthRoute>

            <Redirect to="/not-found" />
          </Switch>
        </div>
      </React.Fragment>
    );
  }
}

const mapStateToProps = (state) => ({
  isAuthenticated: state.auth.isAuthenticated,
  user: state.auth.user,
});

export default connect(mapStateToProps)(App);

路由守卫组件AuthRoute.js代码

const AuthRoute = (props) => {
  const { isAuthenticated, user, type } = props;

  if (type == "guest" && isAuthenticated) {
    return <Redirect to={`/events/${user._id}`} />;
  } else if (type == "private" && !isAuthenticated)
    return <Redirect to={`/login`} />;
  return <Route {...props} />;
};

const mapStateToProps = (state) => ({
  isAuthenticated: state.auth.isAuthenticated,
  userloaded: state.auth.userloaded,
  user: state.auth.user,
});

export default connect(mapStateToProps)(AuthRoute);

Redux认证模块reducer代码authReducer.js

const initialState = {
  token: localStorage.getItem("token"),
  isAuthenticated: null,
  userloaded: false,
  isLoading: false,
  user: null,
};

export default function (state = initialState, action) {
  switch (action.type) {
    case USER_LOADING:
      return {
        ...state,
        isLoading: true,
      };
    case USER_LOADED:
      return {
        ...state,
        user: action.payload,
        userloaded: true,
        isAuthenticated: true,
        isLoading: false,
      };
    case LOGIN_SUCCESS:
    case REGISTER_SUCCESS:
      localStorage.setItem("token", action.payload.token);
      return {
        ...state,
        userloaded: true,
        isAuthenticated: true,
        isLoading: false,
        ...action.payload,
      };
    case AUTH_ERROR:
    case LOGIN_FAIL:
    case LOGOUT_SUCCESS:
    case REGISTER_FAIL:
      localStorage.removeItem("token");
      return {
        ...state,
        token: null,
        user: null,
        userloaded: false,
        isAuthenticated: false,
        isLoading: false,
      };
    default:
      return state;
  }
}

Redux认证模块action代码authActions.js

export const loadUser = () => (dispatch, getState) => {
  dispatch({ type: USER_LOADING });

  axios
    .get("/api/auth/user", tokenConfig(getState))
    .then((res) =>
      dispatch({
        type: USER_LOADED,
        payload: res.data,
      })
    )
    .catch((err) => {
      dispatch(returnErrors(err.response.data, err.response.status));
      dispatch({ type: AUTH_ERROR });
    });
};

export const register =
  ({ name, email, password }) =>
  (dispatch) => {
    const config = {
      headers: {
        "Content-Type": "application/json",
      },
    };
    const body = JSON.stringify({ name, email, password });

    axios
      .post("/api/users", body, config)
      .then((res) =>
        dispatch({
          type: REGISTER_SUCCESS,
          payload: res.data,
        })
      )
      .catch((err) => {
        dispatch(
          returnErrors(err.response.data, err.response.status, "REGISTER_FAIL")
        );
        dispatch({ type: REGISTER_FAIL });
      });
  };

export const login =
  ({ email, password }) =>
  (dispatch) => {
    const config = {
      headers: {
        "Content-Type": "application/json",
      },
    };
    const body = JSON.stringify({ email, password });

    axios
      .post("/api/auth", body, config)
      .then((res) =>
        dispatch({
          type: LOGIN_SUCCESS,
          payload: res.data,
        })
      )
      .catch((err) => {
        dispatch(
          returnErrors(err.response.data, err.response.status, "LOGIN_FAIL")
        );
        dispatch({ type: LOGIN_FAIL });
      });
  };

export const logout = () => {
  return {
    type: LOGOUT_SUCCESS,
  };
};

export const tokenConfig = (getState) => {
  const token = getState().auth.token;
  const config = {
    headers: {
      "Content-type": "application/json",
    },
  };
  if (token) {
    config.headers["x-auth-token"] = token;
  }
  return config;
};

现存异常

  • 异常1:用户登录后触发重定向逻辑时,Redux store中存储的user对象的_id字段为undefined,页面会跳转到/events/undefined路径;只有手动刷新页面后,才能获取到正确的用户_id,跳转至正确的用户专属路径(例如/events/62a6503c9e8a60254ed94ff1)。
  • 异常2:刷新应用页面时,登录页面会短暂闪现,之后才会加载对应正常页面。

根因定位

异常1根因

登录接口/api/auth返回的payload通常仅包含token字段,不返回完整用户信息(含_id),但当前LOGIN_SUCCESS的reducer逻辑直接展开整个payload存入store,还错误将userloaded标记为true,导致路由守卫判定用户信息已加载完成,此时user值为null,取user._id自然得到undefined。
刷新页面时componentDidMount会触发loadUser(),该接口会拉取完整用户信息存入store,因此刷新后能拿到正确的_id。

异常2根因

页面刷新时,loadUser()是异步请求,请求返回前isAuthenticated初始值为null,路由守卫逻辑仅判断!isAuthenticated就跳转登录页,没有区分「未登录」和「用户信息加载中」两种状态,导致加载过程中直接触发登录页重定向,等用户信息加载完成后又跳回正常页面,出现登录页短暂闪现问题。

修复方案

1. 调整authReducer逻辑

修改LOGIN_SUCCESS/REGISTER_SUCCESS处理规则:仅存储token,不提前标记userloaded为true,登录/注册成功后不直接写入user值,统一通过loadUser()拉取完整用户信息。
核心修改片段:

case LOGIN_SUCCESS:
case REGISTER_SUCCESS:
  localStorage.setItem("token", action.payload.token);
  return {
    ...state,
    token: action.payload.token,
    isLoading: false,
    // 移除错误的userloaded: true和...action.payload展开逻辑
  };

2. 补充login/register action逻辑

登录、注册接口请求成功、token存入store后,立即dispatchloadUser()拉取完整用户信息,保证后续重定向时能拿到包含_id的完整user对象。
login核心修改片段:

axios
  .post("/api/auth", body, config)
  .then((res) => {
    dispatch({
      type: LOGIN_SUCCESS,
      payload: res.data,
    });
    // 登录成功后立即拉取完整用户信息
    dispatch(loadUser());
  })

register action做相同修改,注册成功后追加dispatch(loadUser())即可。

3. 改造AuthRoute路由守卫

增加加载状态判断:当用户信息处于加载中(isLoading为true,或本地存在token但userloaded为false)时,渲染全局加载组件,不执行路由跳转,等用户信息加载完成后再走鉴权重定向逻辑,从根源避免登录页闪现、user为undefined时跳转的问题。
修改后的AuthRoute完整代码:

const AuthRoute = (props) => {
  const { isAuthenticated, user, type, isLoading, userloaded } = props;

  // 加载状态下渲染全局加载提示,不做跳转
  if (isLoading || (localStorage.getItem("token") && !userloaded)) {
    return <div className="global-loading">页面加载中...</div>;
  }

  if (type == "guest" && isAuthenticated) {
    // 此时user已完成加载,可安全读取_id
    return <Redirect to={`/events/${user._id}`} />;
  } else if (type == "private" && !isAuthenticated) {
    return <Redirect to={`/login`} />;
  }
  return <Route {...props} />;
};

const mapStateToProps = (state) => ({
  isAuthenticated: state.auth.isAuthenticated,
  userloaded: state.auth.userloaded,
  isLoading: state.auth.isLoading,
  user: state.auth.user,
});

export default connect(mapStateToProps)(AuthRoute);

4. 补充动态路由规则

当前路由表仅配置了/events路径,没有配置/events/:userId的动态路由,需要在App组件的Switch中补充对应规则,否则跳转到带id的路径后会命中404重定向:

<AuthRoute path="/events/:userId" type="private">
  <SimpleSlider />
</AuthRoute>

修复效果

  • 登录/注册成功后,会先拉取完整用户信息再触发重定向,直接跳转到携带正确用户_id的专属主页,不会出现/events/undefined问题。
  • 页面刷新时,加载状态下会展示全局加载提示,等用户信息校验完成后直接渲染目标页面,不会出现登录页短暂闪现问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 09:27:29