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

React状态管理问题:返回首页时保留搜索状态与结果

保留搜索状态:从图书详情页返回首页时恢复表单与搜索结果

我用Google Books API做了个图书查询应用,设置了两个路由:首页包含搜索表单和结果列表,详情页展示单本图书的完整信息。现在从详情页点击返回首页的按钮,只会显示空表单,之前的搜索结果和表单输入内容都丢失了,怎么修改才能返回时保留这些状态?

现有代码

BookDetails.jsx

import { useParams } from "react-router-dom";
import fetchBookDetails from "../hooks/fetchBookDetails";
import { useQuery } from "@tanstack/react-query";
import { Link } from "react-router-dom";
import notFoundImage from "../assets/react.svg";
import Header from "./Header";

const BookDetails = () => {
  const { id } = useParams();
  const results = useQuery(["book", id], fetchBookDetails);

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

  if (results.isError) {
    return <div>Book with id {id} not found.</div>;
  }

  if (results.data) {
    return (
      <div>
        <Header />
        <Link to="/">
          <button className="back">Back</button>
        </Link>
        <div className="book-details">
          <img
            src={
              results.data.volumeInfo.imageLinks
                ? results.data.volumeInfo.imageLinks.thumbnail
                : notFoundImage
            }
            alt={results.data.volumeInfo.title}
          />
          <h1>{results.data.volumeInfo.title}</h1>
          <p>{results.data.volumeInfo.authors}</p>
          
        </div>
      </div>
    );
  }
};

export default BookDetails;

SearchForm.jsx

const SearchForm = ({ onSubmit }) => {
  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(e.target.elements.book.value);
  };

  return (
    <>
      {" "}
      <div>
        <form onSubmit={handleSubmit}>
          <label htmlFor="book">
            Book Name:
            <input type="text" name="book" />
          </label>
          <button type="submit">Submit</button>
        </form>
      </div>
    </>
  );
};

export default SearchForm;

SearchResults.jsx

import React from "react";
import { Link } from "react-router-dom";
import notFoundImage from "../assets/react.svg";

const SearchResults = ({ results }) => {
  return results.isLoading ? (
    <div>Loading...</div>
  ) : results.isError ? (
    <div>{results.error.message}</div>
  ) : (
    <div className="books">
      {results.data.items.map((item, i) => {
        return (
          <Link to={`/book/${item.id}`} key={item.id}>
            <div key={item.id} className="book border-2-red-500">
              <p>{i + 1}</p>

              <img
                src={
                  item.volumeInfo.imageLinks
                    ? item.volumeInfo.imageLinks.thumbnail
                    : notFoundImage
                }
                alt={item.volumeInfo.title}
              />

              <h3>{item.volumeInfo.title}</h3>
              <p>{item.volumeInfo.authors}</p>
            </div>
          </Link>
        );
      })}
    </div>
  );
};

export default SearchResults;

Home.jsx

import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import fetchBooks from "../hooks/fetchBooks";
import SearchResults from "./SearchResults";

import SearchForm from "./SearchForm";
import Header from "./Header";

const Home = () => {
  const [book, setBook] = useState(null);

  const results = useQuery(["search", book], fetchBooks);

  return (
    <main>
      <Header />

      <SearchForm onSubmit={setBook} />
      <SearchResults results={results} />
    </main>
  );
};

export default Home;

App.jsx

import "./App.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Home from "./components/Home";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import BookDetails from "./components/BookDetails";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: Infinity,
      cacheTime: Infinity,
    },
  },
});

function App() {
  return (
    <BrowserRouter>
      <QueryClientProvider client={queryClient}>
        <Routes>
          <Route path="/book/:id" element={<BookDetails />} />
          <Route path="/" element={<Home />} />
        </Routes>
      </QueryClientProvider>
    </BrowserRouter>
  );
}

export default App;

解决方案

核心思路是把搜索状态同步到URL的查询参数中,这样路由切换时状态不会丢失,具体分三步修改:

1. 首页绑定URL查询参数,替换本地状态

修改Home.jsx,用useSearchParams读写URL参数,替代原来的useState:

import { useSearchParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import fetchBooks from "../hooks/fetchBooks";
import SearchResults from "./SearchResults";
import SearchForm from "./SearchForm";
import Header from "./Header";

const Home = () => {
  const [searchParams, setSearchParams] = useSearchParams();
  const book = searchParams.get("q");

  // 只有当存在搜索关键词时才发起请求
  const results = useQuery(["search", book], fetchBooks, {
    enabled: !!book,
  });

  const handleSearch = (query) => {
    // 把搜索关键词同步到URL参数
    setSearchParams(query ? { q: query } : {});
  };

  return (
    <main>
      <Header />
      {/* 把当前搜索关键词传给表单做回显 */}
      <SearchForm onSubmit={handleSearch} initialValue={book} />
      {/* 有搜索关键词时才显示结果列表 */}
      {book && <SearchResults results={results} />}
    </main>
  );
};

export default Home;

2. 搜索表单支持回显初始值

修改SearchForm.jsx,添加initialValue属性,用来回显之前的搜索关键词:

import { useEffect, useRef } from "react";

const SearchForm = ({ onSubmit, initialValue = "" }) => {
  const inputRef = useRef(null);

  // 当初始值变化时更新输入框内容
  useEffect(() => {
    if (inputRef.current) {
      inputRef.current.value = initialValue;
    }
  }, [initialValue]);

  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(e.target.elements.book.value.trim());
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <label htmlFor="book">
          Book Name:
          <input type="text" name="book" ref={inputRef} />
        </label>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

export default SearchForm;

3. 详情页返回时携带搜索参数

修改BookDetails.jsx,读取当前URL的搜索参数,拼接到返回链接中:

import { useParams, useSearchParams, Link } from "react-router-dom";
import fetchBookDetails from "../hooks/fetchBookDetails";
import { useQuery } from "@tanstack/react-query";
import notFoundImage from "../assets/react.svg";
import Header from "./Header";

const BookDetails = () => {
  const { id } = useParams();
  const [searchParams] = useSearchParams();
  const results = useQuery(["book", id], fetchBookDetails);

  // 构建返回链接,带上之前的搜索参数
  const backUrl = searchParams.toString() ? `/?${searchParams.toString()}` : "/";

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

  if (results.isError) {
    return <div>Book with id {id} not found.</div>;
  }

  if (results.data) {
    return (
      <div>
        <Header />
        <Link to={backUrl}>
          <button className="back">Back</button>
        </Link>
        <div className="book-details">
          <img
            src={
              results.data.volumeInfo.imageLinks
                ? results.data.volumeInfo.imageLinks.thumbnail
                : notFoundImage
            }
            alt={results.data.volumeInfo.title}
          />
          <h1>{results.data.volumeInfo.title}</h1>
          <p>{results.data.volumeInfo.authors?.join(", ")}</p>
        </div>
      </div>
    );
  }
};

export default BookDetails;

现在测试流程:搜索关键词→进入详情页→点击返回按钮,首页会自动回显搜索关键词并展示对应的结果列表,状态完全保留。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 02:01:06