Gatsby应用URL变更后页面未更新,如何修复?
问题
我有一个带分页按钮的Gatsby应用,点击「上一页/下一页」按钮时URL会变化,但页面不会重新发起API请求刷新数据。以下是我的characters.tsx代码:
import { Link, PageProps } from "gatsby"; import React, { useEffect, useState, useCallback } from "react"; import Layout from "../components/layout"; import * as styles from '../../styles/Home.module.css'; import gqlLocal from "../../functions/graphqlLocal"; const AquireData = async (params: any) => { let inputParam = 1; if (!params || params?.page == null) { inputParam = 1; } else if (isNaN(params.page)) { inputParam = 1; } else if (parseInt(params.page) < 0 || parseInt(params.page) > 42) { inputParam = 1; } else { inputParam = params.page; } const processedParam = inputParam; const data = { page: processedParam }; const JSONdata = JSON.stringify(data); const response = await gqlLocal(JSONdata); const bodyResp = response.body const result = await JSON.parse(bodyResp); return [params.page, result]; } const Characters: React.FC<PageProps> = () => { const starterJsonPreParse = `[ { "id": "1", "name": "Rick Sanchez", "species": "Human", "status": "Alive" }, { "id": "2", "name": "Morty Smith", "species": "Human", "status": "Alive" } ]`; const starterJson = JSON.parse(starterJsonPreParse); const [pageUpVal, setPageUpVal] = useState('/characters'); const [pageDownVal, setPageDownVal] = useState('/characters'); const [fetchedParamsNum, setFetchedParamsNum] = useState(1); const [fetchedParamsJson, setFetchedParamsJson] = useState(starterJson); const fetchedParamsWrapped = useCallback( async () => { const params = new Proxy(new URLSearchParams(window.location.search), { get: (searchParams, prop) => searchParams.get(prop), }); const fetchedParams = await AquireData(params); setFetchedParamsNum(fetchedParams[0]); setFetchedParamsJson(fetchedParams[1]); }, []) useEffect(() => { fetchedParamsWrapped(); const payloadLoadedUp = Number(fetchedParamsNum) + 1; if (payloadLoadedUp > 42) { setPageUpVal('/characters/?page=42'); } else { setPageUpVal('/characters/?page=' + payloadLoadedUp.toString()); } const payloadLoadedDown = Number(fetchedParamsNum) - 1; if (payloadLoadedDown < 1) { setPageDownVal('/characters/?page=1'); } else { setPageDownVal('/characters/?page=' + payloadLoadedDown.toString()); } }, [fetchedParamsNum, fetchedParamsWrapped]) return ( <Layout> <div className={styles.container}> <h2 className="text-4xl font-extrabold dark:text-white"> <title>Create Next App</title> <meta name="description" content="Generated by create next app" /> <link rel="icon" href="/favicon.ico" /> </h2> <div> <div> Page: {fetchedParamsNum} </div> <br/> <div className="container flex flex-wrap items-center justify-between mx-auto"> <Link to={pageDownVal} className="inline-flex items-center px-4 py-2 mr-3 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"> <svg aria-hidden="true" className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z" clipRule="evenodd"></path></svg> Previous </Link> <Link to={pageUpVal} className="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"> Next <svg aria-hidden="true" className="w-5 h-5 ml-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z" clipRule="evenodd"></path></svg> </Link> </div> </div> <br /> <div className="overflow-x-auto relative shadow-md sm:rounded-lg"> <table className="w-full text-sm text-left text-gray-500 dark:text-gray-400"> <thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"> <tr> <th scope="col" className="py-3 px-6">NAME</th> <th scope="col" className="py-3 px-6">ROLE</th> <th scope="col" className="py-3 px-6">STATUS</th> </tr> </thead> <tbody> {fetchedParamsJson?.map((section: any) => { return ( <tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600" key={section.id}> <td className="py-4 px-6">{section.name}</td> <td className="py-4 px-6">{section.species}</td> <td className="py-4 px-6">{section.status}</td> </tr> ) })} </tbody> </table> </div> </div> </Layout> ) } export default Characters;
点击分页按钮后,URL的page参数会改变,但页面数据始终是初始值,没有触发新的API请求。
解决方案
问题核心是Gatsby的客户端路由机制:使用Link组件跳转时,组件不会重新挂载,而原代码没有监听URL参数变化,导致数据获取逻辑无法自动触发。按以下方式修改:
关键修改点
- 引入
useLocation钩子监听URL变化,替代直接读取window.location的方式 - 将URL的
search参数加入依赖数组,确保参数变化时重新执行数据获取 - 简化分页链接的计算逻辑,避免依赖滞后的状态值
- 重构数据请求函数,优化参数处理逻辑
修改后的完整代码
import { Link, PageProps, useLocation } from "gatsby"; import React, { useEffect, useState, useCallback } from "react"; import Layout from "../components/layout"; import * as styles from '../../styles/Home.module.css'; import gqlLocal from "../../functions/graphqlLocal"; const AquireData = async (pageParam: string | null) => { let inputParam = 1; if (pageParam) { const parsedPage = parseInt(pageParam); if (!isNaN(parsedPage) && parsedPage >= 1 && parsedPage <= 42) { inputParam = parsedPage; } } const data = { page: inputParam }; const JSONdata = JSON.stringify(data); const response = await gqlLocal(JSONdata); const bodyResp = response.body; const result = await JSON.parse(bodyResp); return [inputParam, result]; } const Characters: React.FC<PageProps> = () => { const location = useLocation(); const starterJson = [ { "id": "1", "name": "Rick Sanchez", "species": "Human", "status": "Alive" }, { "id": "2", "name": "Morty Smith", "species": "Human", "status": "Alive" } ]; const [currentPage, setCurrentPage] = useState(1); const [characterData, setCharacterData] = useState(starterJson); const fetchCharacters = useCallback(async () => { const searchParams = new URLSearchParams(location.search); const pageParam = searchParams.get('page'); const [fetchedPage, fetchedData] = await AquireData(pageParam); setCurrentPage(fetchedPage); setCharacterData(fetchedData); }, [location.search]) useEffect(() => { fetchCharacters(); }, [fetchCharacters]) // 计算上下页链接 const nextPage = Math.min(currentPage + 1, 42); const prevPage = Math.max(currentPage - 1, 1); const pageUpVal = `/characters/?page=${nextPage}`; const pageDownVal = `/characters/?page=${prevPage}`; return ( <Layout> <div className={styles.container}> <h2 className="text-4xl font-extrabold dark:text-white"> <title>Characters List</title> <meta name="description" content="Rick and Morty Characters List" /> <link rel="icon" href="/favicon.ico" /> </h2> <div> <div> Page: {currentPage} </div> <br/> <div className="container flex flex-wrap items-center justify-between mx-auto"> <Link to={pageDownVal} className="inline-flex items-center px-4 py-2 mr-3 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"> <svg aria-hidden="true" className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z" clipRule="evenodd"></path></svg> Previous </Link> <Link to={pageUpVal} className="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"> Next <svg aria-hidden="true" className="w-5 h-5 ml-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z" clipRule="evenodd"></path></svg> </Link> </div> </div> <br /> <div className="overflow-x-auto relative shadow-md sm:rounded-lg"> <table className="w-full text-sm text-left text-gray-500 dark:text-gray-400"> <thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"> <tr> <th scope="col" className="py-3 px-6">NAME</th> <th scope="col" className="py-3 px-6">SPECIES</th> <th scope="col" className="py-3 px-6">STATUS</th> </tr> </thead> <tbody> {characterData?.map((section: any) => ( <tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600" key={section.id}> <td className="py-4 px-6">{section.name}</td> <td className="py-4 px-6">{section.species}</td> <td className="py-4 px-6">{section.status}</td> </tr> ))} </tbody> </table> </div> </div> </Layout> ) } export default Characters;
说明
useLocation钩子会在URL变化时触发组件更新,确保fetchCharacters函数能感知到分页参数变化- 将
location.search加入useCallback依赖,保证URL参数变化时重新生成请求函数,触发useEffect执行 - 直接基于
currentPage状态计算分页链接,避免了原代码中状态更新滞后的问题 - 简化了
AquireData函数的参数校验逻辑,避免返回无效的页码值
内容的提问来源于stack exchange,提问作者ChristianOConnor
相关产品推荐
相关产品推荐

