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

React Router v6导航后BookmarkList组件数据未更新问题求助

问题描述

使用react-router-dom v6开发项目,BookmarkList组件负责展示书签列表,支持添加、删除书签操作。当前遇到的问题:在AddBookmark组件中添加书签后,调用navigate("/bookmark")导航至BookmarkList组件,组件虽会重新渲染,但自定义Hook未从服务器获取最新书签列表(刷新页面可正常显示最新数据)。

相关代码

BookmarkList组件

import ReactCountryFlag from "react-country-flag";
import { useBookmarks } from "../context/BookmarksProvider";
import { Link } from "react-router-dom";
import { closePopup } from "../map/Map";
import { HiTrash } from "react-icons/hi";

export default function BookmarkList() {
  const { bookmarks, deleteBookmark } = useBookmarks();

  function handleBookmarkDelete(e, id) {
    e.preventDefault();
    deleteBookmark(id);    
  }
  
  return (
    <div>
      <h2>Bookmark List</h2>
      <div className="bookmarkList">
        {bookmarks.map((bookmark) => {
          return (
            <Link
              key={bookmark.id}
              to={`${bookmark.id}?lat=${bookmark.latitude}&lng=${bookmark.longitude}`}
              onClick={closePopup}
            >
              <div className="bookmarkItem">
                <div>
                  <ReactCountryFlag svg countryCode={bookmark.countryCode} />
                  &nbsp; <strong>{bookmark.cityName}</strong> &nbsp;{" "}
                  <span>{bookmark.country}</span>
                </div>
                <button
                  type="button"
                  onClick={(e) => {
                    handleBookmarkDelete(e, bookmark.id);
                  }}
                >
                  <HiTrash className="trash" />
                </button>
              </div>
            </Link>
          );
        })}
      </div>
    </div>
  );
}

AddBookmark组件

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAnglesLeft } from "@fortawesome/free-solid-svg-icons";
import { useNavigate, useSearchParams } from "react-router-dom";
import useFetch from "../../hooks/useFetch";
import { useBookmarks } from "../context/BookmarksProvider";
import { useEffect, useState } from "react";
import {v4 as uuidv4} from 'uuid';


export default function AddBookmarks() {
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();
  const [cityNameState, setCityNameState] = useState("");
  const [countryNameState, setCountryNameState] = useState("");
  const { addBookmark } = useBookmarks();
  const lat = searchParams.get("lat");
  const lng = searchParams.get("lng");

  const { isLoading, data } = useFetch(
    "https://api.bigdatacloud.net/data/reverse-geocode-client",
    `latitude=${lat}&longitude=${lng}`
  );

  useEffect(() => {
    if (data) {
      setCityNameState(data.city);
      setCountryNameState(data.countryName);
    }
  }, [data]);

  if (isLoading) return <div>Loading ...</div>;

  function handleSubmit(e) {
    e.preventDefault();
    
    const newBookmark = {
      cityName: cityNameState || data.locality,
      countryName: countryNameState,
      countryCode: data.countryCode,
      latitude: `${data.latitude}`,
      longitude: `${data.longitude}`,
      host_location: data.city + " " + data.countryName,
      id: uuidv4(),
    };

    addBookmark(newBookmark);
    navigate("/bookmark");
  }

  return (
    <div className="capitalize">
      <h2 className="font-bold">add bookmark</h2>
      <form className="mt-2 [&>div>label]:text-sm" onSubmit={handleSubmit}>
        <div className="flex flex-col">
          <label className="">city:</label>
          <input
            className="border-b border-0 border-solid mt-2"
            type="text"
            required
            value={cityNameState}
            onChange={(e) => setCityNameState(e.target.value)}
          />
        </div>
        <div className="flex flex-col mt-4">
          <label>country:</label>
          <input
            className="border-b border-0 border-solid mt-2"
            type="text"
            required
            value={countryNameState}
            onChange={(e) => setCountryNameState(e.target.value)}
          />
        </div>
        <div className="mt-5 flex justify-between">
          <button
            onClick={() => {
              navigate(-1);
            }}
            type="button"
            className="border   border-slate-400 border-solid rounded-md flex space-x-1 items-center p-0.5 px-1"
          >
            <FontAwesomeIcon icon={faAnglesLeft} size="xs" />
            <p>Back</p>
          </button>
          <button className="bg-purple-600 text-white px-3 py-0.5 rounded-md">
            save
          </button>
        </div>
      </form>
    </div>
  );
}

useFetch自定义Hook

import axios, { AxiosError } from "axios";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";

export default function useFetch(url, query = "") {
  const [data, setData] = useState();
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    setIsLoading(true);
    axios
      .get(`${url}?${query}`)
      .then((response) => {
        setData(response.data);
      })
      .catch((err) => {
        toast.error(err.message);
      })
      .finally(() => setIsLoading(false));
  }, [url, query]);

  return { isLoading, data };
}

App.jsx路由配置

<Routes>
  <Route path="/" element={<HotelsList />} />
  <Route path="/search" element={<SearchLayout />}>
    <Route index element={<Hotels />} />
    <Route path="Hotels/:id" element={<SingleHotel />} />
  </Route>
  <Route path="/bookmark" element={<BookmarkLayout />}>
    <Route index element={<BookmarkList />} />
    <Route path=":id" element={<SingleBookmark />} />
    <Route path="add" element={<AddBookmarks />} />
  </Route>
  <Route path="/404" element={<ErrorPage />} />
  <Route path="*" element={<Navigate to="/404" />} />
</Routes>

已尝试在BookmarkList组件中添加useEffect、修改路由路径为"/bookmark/",均未解决问题。


问题分析与解决

核心问题在于书签添加完成后,没有触发服务器端书签列表的重新获取,且你未提供BookmarksProvider的实现,这是关键逻辑环节。结合现有代码推断,给出以下解决步骤:

1. 完善addBookmark的异步提交逻辑

确保addBookmark先将新书签提交到服务器,成功后再触发列表刷新:

// BookmarksProvider中的addBookmark示例实现
const addBookmark = async (newBookmark) => {
  try {
    // 提交新书签到服务器
    await axios.post('/api/bookmarks', newBookmark);
    // 提交成功后,重新拉取最新书签列表
    fetchBookmarks(); // fetchBookmarks是你定义的获取服务器书签列表的方法
  } catch (err) {
    toast.error('添加书签失败');
  }
};

2. 给useFetch添加手动刷新能力

修改useFetch,使其支持手动触发请求,方便在添加书签后主动刷新数据:

export default function useFetch(url, query = "") {
  const [data, setData] = useState();
  const [isLoading, setIsLoading] = useState(true);

  const fetchData = async () => {
    setIsLoading(true);
    try {
      const response = await axios.get(`${url}?${query}`);
      setData(response.data);
    } catch (err) {
      toast.error(err.message);
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, [url, query]);

  return { isLoading, data, fetchData }; // 返回手动刷新方法
}

3. 调整导航时机

等待书签提交并刷新列表完成后,再执行导航操作:

// AddBookmark组件的handleSubmit修改为异步函数
async function handleSubmit(e) {
  e.preventDefault();
  
  const newBookmark = {
    cityName: cityNameState || data.locality,
    countryName: countryNameState,
    countryCode: data.countryCode,
    latitude: `${data.latitude}`,
    longitude: `${data.longitude}`,
    host_location: data.city + " " + data.countryName,
    id: uuidv4(),
  };

  await addBookmark(newBookmark); // 等待添加操作完成
  navigate("/bookmark");
}

可选:添加乐观更新提升体验

如果想优化用户体验,可以先在本地状态添加新书签,再同步到服务器,失败时回滚:

// BookmarksProvider中的addBookmark优化
const addBookmark = async (newBookmark) => {
  // 先本地添加(乐观更新)
  setBookmarks(prev => [...prev, newBookmark]);
  try {
    await axios.post('/api/bookmarks', newBookmark);
  } catch (err) {
    // 提交失败,回滚本地状态
    setBookmarks(prev => prev.filter(item => item.id !== newBookmark.id));
    toast.error('添加书签失败');
  }
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 19:03:13