apollo useSuspenseQuery вызывает постоянные перерисовки

Вопрос или проблема

У меня есть следующий компонент – LocationStats, который выполняет запрос с использованием suspense. Запрос вызывает бесконечные повторные рендеры. Я вижу, что он отправляет запрос на сервер, и как только получает ответ, снова запускает тот же запрос, и так продолжается. Если я удаляю suspense-запрос и использую старый добрый запрос, он ведет себя нормально, загружает данные один раз и все.

Использую последнюю версию Apollo 3.11.8 и последнюю версию React. Почему он так себя ведет?

export const LocationStats: React.FC<GeographyDataProps> = ({ locationId, initialMeasure }) => {
    const timeSeriesRequests = useMemo(() => {
        const now = DateTime.now();
        return Object.values(timeseriesMetadata).reduce<TimeSeriesRequestInput[]>((acc, value) => {
            return [
                ...acc,
                { id: locationId, name: value, fromDate: now.toISO(), toDate: now.toISO() },
                { id: locationId, name: value, fromDate: now.minus({ years: 5 }).toISO(), toDate: now.toISO() },
            ];
        }, []);
    }, [locationId]);
    const {
        data: {
            timeSeriesEndPoint: { getTimeSeries: entries },
        },
    } = useSuspenseQuery<LocationStatsQuery>(locationStatsQuery, {
        variables: {
            timeSeriesRequests,
        },
        fetchPolicy: "no-cache",
    });
    const allData = useMemo(() => {
        return Object.entries(timeseriesMetadata).reduce<Record<Measure | `current${Measure}`, Timeseries>>(
            (acc, [key], index) => {
                const dataIndex = index * 2;
                acc[key as Measure] = {
                    metadata: { label: "", description: "" },
                    data: entries[dataIndex].data,
                };
                acc[`current${key as Measure}`] = {
                    metadata: { label: "", description: "" },
                    data: entries[dataIndex + 1].data,
                };
                return acc;
            },
            {} as Record<Measure | `current${Measure}`, Timeseries>,
        );
    }, [entries]);
const [activeTab, setActiveTab] = useState<Measure>(initialMeasure ?? Measure.Load);
return (
        <>
            <div className="self-stretch px-2.5 pt-1.5 border-b border-theme-border-base/20 justify-start items-start gap-1.5 inline-flex">
                {Object.values(Measure).map(measure => (
                    <Tab key={measure} active={activeTab === measure} onTabActivated={() => setActiveTab(measure)}>
                        {measure}
                    </Tab>
                ))}
            </div>
            <div className="self-stretch grow shrink basis-0 p-3 flex-col justify-center items-start gap-3 flex">
                <TimeseriesChart title={activeTab} data={allData[activeTab].data} metadata={allData[activeTab].metadata} />
            </div>
        </>
    );

// компонент, использующий вышеупомянутый
const Fallback = <LoadingIndicator text="Загрузка данных. Пожалуйста, подождите..." />;
export const DetailsPanel = ({ highlightedGeograhy, geographyType }: DetailsPanelProps) => {
    return (
        <div className="bg-gray-100 rounded border border-theme-border-base flex-col justify-start items-start inline-flex flex-auto">
            <div className="self-stretch h-10 p-3 rounded-tl rounded-tr border justify-start items-center gap-2.5 inline-flex">
                <div className="grow shrink basis-0 h-4 justify-start items-center gap-1.5 flex">
                    <div className="text-xs">{geographyType}</div>
                    <div className="grow shrink basis-0 text-xs font-bold">{highlightedGeograhy?.name}</div>
                </div>
                <div className="justify-start items-center gap-1.5 flex">
                    <div className="w-px self-stretch bg-[#d9d9d9]" />
                    <ToggleButton className="chevron_left rounded border " title="Предыдущий" />
                    <ToggleButton className="navigate_next rounded border " title="Следующий" />
                    <ToggleButton className="before:icon !icon-close rounded border border-theme-border-darker" title="Закрыть" />
                </div>
            </div>
            <Suspense fallback={Fallback}>
                <LocationStats locationId={highlightedGeograhy.id} initialMeasure={Measure.Capacity} />
            </Suspense>
        </div>
    );
};

Ответ или решение

Причины бесконечных перерисовок при использовании useSuspenseQuery в Apollo

При использовании useSuspenseQuery в вашей компоненте LocationStats вы столкнулись с проблемой, заключающейся в бесконечных перерисовках, что вызывает постоянное отправление запросов на сервер. Давайте разберемся в причинах данной проблемы и возможных путях её решения.

Проблема

Согласно представленному коду, каждый раз, когда компонент LocationStats обновляется, создается новый массив timeSeriesRequests. Этот массив генерируется в useMemo, который зависит от locationId. Однако в useSuspenseQuery вы используете параметр fetchPolicy: "no-cache", который означает, что Apollo всегда будет запрашивать свежие данные с сервера, игнорируя кэш. Это может привести к ситуации, когда ваш компонент инициирует новые запросы каждый раз, когда он перерисовывается, что вызывает бесконечный цикл рендеринга.

Возможные Решения

  1. Измените fetchPolicy:
    Попробуйте заменить fetchPolicy: "no-cache" на более подходящую стратегию, например:

    fetchPolicy: "cache-first"

    Это позволит использовать кэширование, что может предотвратить повторные запросы при тех же входных параметрах.

  2. Оптимизация useMemo:
    Убедитесь, что timeSeriesRequests не пересоздается без необходимости. Если locationId остается неизменным, то массив timeSeriesRequests не должен пересоздаваться. Например, вы можете использовать locationId как зависимость useMemo, но вам нужно будет убедиться, что его изменение действительно необходимо для создания нового массива.

  3. Проверка зависимостей:
    Проверьте, нет ли других частей вашего компонента или родительских компонентов, которые могут вызывать повторные перерисовки, что может создавать дополнительную нагрузку и вызывать повторные запросы.

  4. Использование настройки для debounce:
    Если запросы происходят слишком быстро, рассмотрите возможность настройки дебаунса для запроса данных. Это позволит уменьшить количество отправляемых запросов на сервер за короткий промежуток времени.

  5. Вынести запросы за пределы компонента:
    Если timeSeriesRequests можно вычислить вне компонента (или оно не зависит от состояния компонента), вы можете вынести его из компонента или использовать React Context для управления состоянием, чтобы избежать зависимостей между запросами и перерисовками.

Пример кода

Вот пример оптимизации вашего кода с предложенными изменениями:

const timeSeriesRequests = useMemo(() => {
    const now = DateTime.now();
    return Object.values(timeseriesMetadata).flatMap(value => [
        { id: locationId, name: value, fromDate: now.toISO(), toDate: now.toISO() },
        { id: locationId, name: value, fromDate: now.minus({ years: 5 }).toISO(), toDate: now.toISO() },
    ]);
}, [locationId]);

const {
    data: {
        timeSeriesEndPoint: { getTimeSeries: entries },
    },
} = useSuspenseQuery<LocationStatsQuery>(locationStatsQuery, {
    variables: { timeSeriesRequests },
    fetchPolicy: "cache-first", // Изменение политики кэширования
});

Заключение

Без бесконечных перерисовок и избыточных запросов на сервер, вы сможете добиться лучшей производительности вашего приложения. Следуя вышеприведённым рекомендациям, вы сможете решить проблему, связавшуюся с использованием useSuspenseQuery в вашем компоненте. Если у вас остались вопросы или потребуется помощь, не стесняйтесь обращаться за уточнениями!

Оцените материал
Добавить комментарий

Капча загружается...