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

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参数变化,导致数据获取逻辑无法自动触发。按以下方式修改:

关键修改点

  1. 引入useLocation钩子监听URL变化,替代直接读取window.location的方式
  2. 将URL的search参数加入依赖数组,确保参数变化时重新执行数据获取
  3. 简化分页链接的计算逻辑,避免依赖滞后的状态值
  4. 重构数据请求函数,优化参数处理逻辑

修改后的完整代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 20:10:50