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
相关产品推荐
相关产品推荐

