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

React+GraphQL+Apollo多用户切换后,无刷新获取新用户信息问题

问题描述

我使用React、GraphQL、Apollo Client、MongoDB Atlas开发一款语录应用,登录时将认证token存储在localStorage中,登出时移除该token。应用内的个人资料页用于展示当前登录用户的详情,但遇到一个问题:用户登出后切换登录另一个账号时,页面仍显示前一位用户的详情,只有刷新页面后才会显示当前登录用户的信息,需要实现无需刷新页面即可获取新用户详情的效果。

问题根源

Apollo Client默认会缓存查询结果,登出操作仅移除了localStorage中的token,但未清除缓存里的旧用户数据;新用户登录时,如果没有主动触发缓存更新或重新查询,组件会继续读取缓存中的旧数据,导致页面展示异常。

解决方案

通过清理Apollo缓存、主动触发查询刷新、优化组件查询逻辑三个方向解决:

  1. 登出时重置Apollo缓存,彻底清除旧用户数据
  2. 登录成功后主动刷新用户资料查询,更新缓存
  3. 优化Profile组件的查询触发时机,确保路由切换时获取最新数据

修改后的关键代码

import React from "react";
import { useNavigate } from "react-router";
import { Link } from "react-router-dom";
import { client } from "../index.js";

function NavBar() {
  const token = localStorage.getItem("token");
  const navigate = useNavigate();
  return (
    <div>
      <nav>
        <div className="nav-wrapper">
          <Link to="/" className="brand-logo left">
            Quotes
          </Link>
          <ul id="nav-mobile" className="right">
            {token ? (
              <>
                <li>
                  <Link to="/profile">Profile</Link>
                </li>
                <li>
                  <Link to="/create">Create</Link>
                </li>
                <li>
                  <button
                    className="red btn"
                    onClick={async() => {
                      localStorage.removeItem("token");
                      await client.resetStore();
                      navigate("/login");
                    }}
                  >
                    Logout
                  </button>
                </li>
              </>
            ) : (
              <>
                <li>
                  <Link to="/login">Login</Link>
                </li>
                <li>
                  <Link to="/signup">Signup</Link>
                </li>
              </>
            )}
          </ul>
        </div>
      </nav>
    </div>
  );
}

export default NavBar;

Login组件(优化登录后缓存更新)

import { useMutation } from "@apollo/client";
import React, { useState } from "react";
import { useNavigate } from "react-router";
import { Link } from "react-router-dom";
import { LOGIN_USER } from "../gqloperations/mutations";

export default function Login() {
  const [formData, setFormData] = useState({ email: "", password: "" });
  const [signinUser, { loading, data, error, client }] = useMutation(LOGIN_USER, {
    refetchQueries: ["getMyProfile"],
    onCompleted(data) {
      localStorage.setItem("token", data.user.token);
      navigate("/");
    },
  });
  const navigate = useNavigate();
  const handleChange = (e) => {
    setFormData({ ...formData, [e.target.name]: e.target.value });
  };
  const handleSubmit = (e) => {
    e.preventDefault();
    signinUser({
      variables: {
        userSignin: formData,
      },
    });
    setFormData({ email: "", password: "" });
  };
  if (loading) return <h1>Loading</h1>;
  return (
    <>
      {error && <div className="red card-panel">{error.message}</div>}
      {data && (
        <div className="green card-panel">
          Login successfully
        </div>
      )}
      <div>
        <h1>Login</h1>
        <form onSubmit={handleSubmit}>
          <input
            type="email"
            name="email"
            value={formData.email}
            onChange={handleChange}
            placeholder="email"
          />
          <input
            type="password"
            name="password"
            value={formData.password}
            onChange={handleChange}
            placeholder="password"
          />
          <Link to="/signup">
            <p>Don't have an account ?</p>
          </Link>
          <button type="submit" className="green btn">
            Login
          </button>
        </form>
      </div>
    </>
  );
}

Profile组件(优化查询触发时机)

import React, { useEffect } from "react";
import { useQuery } from "@apollo/client";
import { GET_MY_PROFILE } from "../gqloperations/queries";
import { useNavigate } from "react-router";

export default function Profile() {
  const { loading, error, data, refetch } = useQuery(GET_MY_PROFILE);
  const navigate = useNavigate();

  // 路由进入时重新查询,确保获取最新用户数据
  useEffect(() => {
    if (localStorage.getItem("token")) {
      refetch();
    } else {
      navigate("/login");
    }
  }, [navigate]);

  if (loading) return <h1>Loading...</h1>;
  if (error) console.log(error);
  
  return (
    <div className="container">
      <div className="center-align">
        <img
          style={{ border: "2px solid black", marginTop: "10px" }}
          className="circle"
          src={`https://robohash.org/${data.user.firstName}.png?size=200x200`}
          alt=""
        />
        <h5>
          {data.user.firstName} {data.user.lastname}
        </h5>
        <h5>Email-{data.user.email}</h5>
      </div>
      <h3>Your quotes</h3>
      {data.user.quotes.map((quote) => {
        return (
          <blockquote key={quote._id}>
            <h6>{quote.name}</h6>
          </blockquote>
        );
      })}
    </div>
  );
}

关键修改说明

  • 登出时调用client.resetStore():清空Apollo缓存的同时重新执行所有活跃查询,彻底清除旧用户数据,避免新登录时读取缓存
  • 登录时保留refetchQueries: ["getMyProfile"]:登录成功后主动触发用户资料查询,更新缓存中的用户数据
  • Profile组件调整useEffect逻辑:在组件挂载/路由切换时检查token并重新查询,确保每次进入个人资料页都获取最新用户信息

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 01:06:20