Вопрос или проблема
У меня есть следующий компонент – 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 всегда будет запрашивать свежие данные с сервера, игнорируя кэш. Это может привести к ситуации, когда ваш компонент инициирует новые запросы каждый раз, когда он перерисовывается, что вызывает бесконечный цикл рендеринга.
Возможные Решения
-
Измените fetchPolicy:
Попробуйте заменитьfetchPolicy: "no-cache"
на более подходящую стратегию, например:fetchPolicy: "cache-first"
Это позволит использовать кэширование, что может предотвратить повторные запросы при тех же входных параметрах.
-
Оптимизация useMemo:
Убедитесь, чтоtimeSeriesRequests
не пересоздается без необходимости. ЕслиlocationId
остается неизменным, то массивtimeSeriesRequests
не должен пересоздаваться. Например, вы можете использоватьlocationId
как зависимостьuseMemo
, но вам нужно будет убедиться, что его изменение действительно необходимо для создания нового массива. -
Проверка зависимостей:
Проверьте, нет ли других частей вашего компонента или родительских компонентов, которые могут вызывать повторные перерисовки, что может создавать дополнительную нагрузку и вызывать повторные запросы. -
Использование настройки для debounce:
Если запросы происходят слишком быстро, рассмотрите возможность настройки дебаунса для запроса данных. Это позволит уменьшить количество отправляемых запросов на сервер за короткий промежуток времени. -
Вынести запросы за пределы компонента:
Если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
в вашем компоненте. Если у вас остались вопросы или потребуется помощь, не стесняйтесь обращаться за уточнениями!