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

React中GraphQL订阅异常:路由跳转后事件列表不更新

问题描述

我用GraphQL客户端搭配React开发应用,主页面展示事件列表,/new路径下设有添加事件的表单。通过GraphQL Playground或新标签页打开/new添加事件时,主页面列表会自动更新;但通过React Router导航至/new添加事件后,主页面的事件列表却无法更新。

原因分析

通过React Router从主页面跳转到/new时,EventList组件会被卸载,之前用subscribeToMore建立的GraphQL订阅会断开。回到主页面时,组件重新挂载并重新订阅,但此时新事件已经创建完成,订阅无法捕获到之前的创建动作,导致列表不更新。另外,当前的CREATE_EVENT mutation没有主动更新Apollo缓存,完全依赖订阅同步数据,这就导致了路由导航场景下的同步失效。

解决方案

可以通过两种方式解决这个问题,推荐优先用方案1,能从根源上确保缓存同步:

方案1:在Mutation中手动更新缓存

修改FormComponent里的useMutation配置,添加update函数,事件创建成功后直接更新GET_EVENTS查询的缓存。不管是哪种方式创建事件,缓存都会即时更新,组件就能渲染最新的事件列表。

同时注意修正onCompleted的写法——之前直接写form.resetFields()会立即执行,要改成箭头函数,确保只有在mutation完成后才重置表单。

方案2:优化组件挂载时的数据获取逻辑

在EventList组件的useQuery中添加fetchPolicy: "cache-and-network",让组件每次挂载时都从网络拉取最新数据,同时用缓存作为兜底,确保回到主页面时能拿到最新事件列表。

完整修改后的代码示例

EventList.js

import React, { useEffect } from "react";
import { useQuery } from "@apollo/client";
import { EVENT_SUBSCRIPTION, GET_EVENTS } from "./queries";
import { List, Divider } from "antd";
import InfiniteScroll from "react-infinite-scroll-component";
import { Link } from "react-router-dom";
import styles from "./styles.module.css";
import Loading from "../../components/Loading";
import EventCounter from "../../components/EventCounter";

function EventList() {
    // 添加fetchPolicy确保每次挂载都获取最新数据
    const { loading, error, data, subscribeToMore } = useQuery(GET_EVENTS, {
        fetchPolicy: "cache-and-network",
    });

    useEffect(() => {
        subscribeToMore({
            document: EVENT_SUBSCRIPTION,
            updateQuery: (prev, { subscriptionData }) => {
                console.log(prev, subscriptionData);
                if (!subscriptionData.data) return prev;
                return {
                    events: [...prev.events, subscriptionData.data.eventCreated],
                };
            },
        });
    }, [subscribeToMore]);

    if (loading) {
        return <Loading />;
    }

    if (error) {
        return <div>Error: {error.message}</div>;
    }

    return (
        <>
            <div className="counter">
                <EventCounter />
            </div>

            <div className={styles.scrollableDiv} id="scrollableDiv">
                <List>
                    <InfiniteScroll dataLength={data.events.length} scrollableTarget="scrollableDiv" endMessage={<Divider plain>It is all, nothing more 🤐</Divider>}>
                        <List
                            dataSource={data.events}
                            renderItem={(item) => (
                                <List.Item key={item.id}>
                                    <List.Item.Meta
                                        title={
                                            <Link className={styles.listItemTitle} to={`/event/${item.id}`}>
                                                {item.title}
                                            </Link>
                                        }
                                        description={
                                            <Link className={styles.listItemDesc} to={`/event/${item.id}`}>
                                                {item.desc}
                                            </Link>
                                        }
                                    />
                                    <div className={styles.date}>{`${item.date} / ${item.from}-${item.to}`}</div>
                                </List.Item>
                            )}></List>
                    </InfiniteScroll>
                </List>
            </div>
        </>
    );
}

export default EventList;

FormComponent.js

import React from "react";
import { Button, DatePicker, Form, Input, Row, Select, TimePicker } from "antd";
import { useMutation, useQuery } from "@apollo/client";
import moment from "moment";
import styles from "./style.module.css";
import { CREATE_EVENT, GET_LOCATIONS, GET_USERS, GET_EVENTS } from "./queries";

function FormComponent() {
    // 添加update函数手动更新缓存
    const [addEvent, { data, loading, error }] = useMutation(CREATE_EVENT, {
        update(cache, { data: { eventCreated } }) {
            const { events } = cache.readQuery({ query: GET_EVENTS });
            cache.writeQuery({
                query: GET_EVENTS,
                data: { events: [...events, eventCreated] },
            });
        },
    });

    const { loading: locationsLoading, error: locationsError, data: locationsData } = useQuery(GET_LOCATIONS);
    const locationsArray = [];
    if (!locationsLoading && !locationsError && locationsData) {
        locationsData.locations.forEach((item) => {
            const data = {
                value: item.id,
                label: item.name,
                title: "location_id",
            };
            locationsArray.push(data);
        });
    }

    const { loading: userLoading, error: userError, data: userData } = useQuery(GET_USERS);
    const usersArray = [];
    if (!userLoading && !userError && userData) {
        userData.users.forEach((item) => {
            const data = {
                value: item.id,
                label: item.username,
                title: "user_id",
            };
            usersArray.push(data);
        });
    }

    const [form] = Form.useForm();

    const disabledDate = (current) => {
        return current && current <= moment().startOf("day");
    };

    const handleFormSubmit = async (values) => {
        const data = {
            title: values.title,
            desc: values.desc,
            date: `${values.date.$y}-${values.date.$M + 1}-${values.date.$D}`,
            from: `${values.hours[0].$H < 9 ? "0" + values.hours[0].$H : values.hours[0].$H}:${values.hours[0].$m < 9 ? "0" + values.hours[0].$m : values.hours[0].$m}`,
            to: `${values.hours[1].$H < 9 ? "0" + values.hours[1].$H : values.hours[1].$H}:${values.hours[1].$m < 9 ? "0" + values.hours[1].$m : values.hours[1].$m}`,
            location_id: values.location_id,
            user_id: values.user_id,
        };
        await addEvent({
            variables: {
                data: data,
            },
            onCompleted: () => form.resetFields(), // 修正为箭头函数,确保mutation完成后再执行
        });
    };

    return (
        <Row>
            <Form className={styles.form} labelCol={{ span: 4 }} wrapperCol={{ span: 24 }} form={form} onFinish={handleFormSubmit}>
                <Form.Item label="Title" name="title" required rules={[{ required: true, message: "Please enter title" }]}>
                    <Input placeholder="Please enter title" />
                </Form.Item>
                <Form.Item label="Description" name="desc" required rules={[{ required: true, message: "Please enter description" }]}>
                    <Input.TextArea placeholder="Please enter description" />
                </Form.Item>
                <Form.Item label="Date" name="date" required rules={[{ required: true, message: "Please enter date" }]}>
                    <DatePicker placeholder="Please select a date" style={{ width: "100%" }} disabledDate={disabledDate} />
                </Form.Item>
                <Form.Item name="hours" label="Event Hours" required>
                    <TimePicker.RangePicker style={{ width: "100%" }} placeholder={["Select start time", "Select end time"]} format="HH:mm" />
                </Form.Item>
                <Form.Item label="Location" name="location_id" required rules={[{ required: true, message: "Please select a location" }]}>
                    <Select placeholder="Please select a location" options={locationsArray} />
                </Form.Item>
                <Form.Item label="User" name="user_id" required rules={[{ required: true, message: "Please select a user" }]}>
                    <Select placeholder="Please select a user" options={usersArray} />
                </Form.Item>
                <Form.Item>
                    <Button type="primary" htmlType="submit">
                        Submit
                    </Button>
                </Form.Item>
            </Form>
        </Row>
    );
}

export default FormComponent;

内容的提问来源于stack exchange,提问作者Enes SEVAL

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 16:25:55