Skip to content

Commit

Permalink
reduces number of requests by standardizing query keys
Browse files Browse the repository at this point in the history
  • Loading branch information
agnlez committed Feb 25, 2025
1 parent 9ea648c commit ec147a0
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 168 deletions.
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@hookform/resolvers": "2.8.8",
"@json2csv/plainjs": "^6.1.3",
"@loaders.gl/core": "3.3.1",
"@lukemorales/query-key-factory": "1.3.4",
"@luma.gl/constants": "8.5.18",
"@maplibre/maplibre-gl-compare": "^0.5.0",
"@radix-ui/react-collapsible": "1.0.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useCallback, useMemo } from 'react';
import { H3HexagonLayer } from '@deck.gl/geo-layers/typed';
import { useQueryClient, UseQueryResult } from '@tanstack/react-query';
import { UseQueryResult } from '@tanstack/react-query';

import Map from '@/components/map';
import DeckLayer from '@/components/map/layers/deck';
Expand All @@ -10,7 +10,7 @@ import MapboxRasterLayer from '@/components/map/layers/maplibre/raster';
import { useH3Data } from 'hooks/h3-data';
import PageLoading from 'containers/page-loading';
import { useYears } from 'hooks/years';
import { CategoryWithLayers } from '@/hooks/layers/getContextualLayers';
import useContextualLayers from '@/hooks/layers/getContextualLayers';

import type { H3HexagonLayerProps } from '@deck.gl/geo-layers/typed';
import type { Dispatch } from 'react';
Expand Down Expand Up @@ -39,13 +39,14 @@ const PreviewMap = ({ selectedLayerId, selectedMaterialId, onStatusChange }: Pre
},
});

const queryClient = useQueryClient();
const contextualLayers = queryClient.getQueryData<CategoryWithLayers[]>(['contextual-layers']);
const { data: contextualLayers } = useContextualLayers();

// @debt given how useContextualLayers is implemented, we cannot parse the data during the query fetching
const selectedLayer = contextualLayers
?.flatMap((category) => category.layers)
.find((layer) => layer.id === selectedLayerId);

const isCogLayer = !!selectedLayer?.tilerUrl;
const isRasterLayer = !!selectedLayer?.tilerUrl;

const { data, isFetching, status } = useH3Data({
id: selectedLayerId,
Expand All @@ -55,29 +56,29 @@ const PreviewMap = ({ selectedLayerId, selectedMaterialId, onStatusChange }: Pre
},
options: {
enabled:
!!selectedLayerId && (selectedLayerId !== 'material' || !!materialYear) && !isCogLayer,
!!selectedLayerId && (selectedLayerId !== 'material' || !!materialYear) && !isRasterLayer,
select: (response) => response.data,
// having placeholder data makes the status always be success
placeholderData: undefined,
},
});

const getLayerFetchingStatus = useCallback(() => {
if (isCogLayer) return 'success';
if (isRasterLayer) return 'success';
if (status === 'error') return status;
if (isFetching) return 'loading';

return status;
}, [isCogLayer, isFetching, status]);
}, [isRasterLayer, isFetching, status]);

useEffect(() => {
onStatusChange?.(getLayerFetchingStatus());
}, [onStatusChange, getLayerFetchingStatus]);

const PreviewLayer = useCallback(() => {
if (!data?.length && !selectedLayer?.tilerUrl) return null;
if ((!data?.length && !selectedLayer?.tilerUrl) || !selectedLayerId) return null;

if (isCogLayer) {
if (isRasterLayer) {
return (
<MapboxRasterLayer
id={selectedLayer.id}
Expand All @@ -98,7 +99,7 @@ const PreviewMap = ({ selectedLayerId, selectedMaterialId, onStatusChange }: Pre
getLineColor={(d) => d.c}
/>
);
}, [data, isCogLayer, selectedLayer]);
}, [data, isRasterLayer, selectedLayer, selectedLayerId]);

const layers = useMemo(() => [{ id: PREVIEW_LAYER_ID, layer: PreviewLayer }], [PreviewLayer]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import LegendTypeBasic from 'components/legend/types/basic';
import LegendTypeCategorical from 'components/legend/types/categorical';
import LegendTypeChoropleth from 'components/legend/types/choropleth';
import LegendTypeGradient from 'components/legend/types/gradient';
import { useContextualLayer } from 'hooks/layers/contextual';
import useContextualLayers from 'hooks/layers/getContextualLayers';

import type { Layer } from 'types';

Expand All @@ -20,9 +18,6 @@ interface ContextualLegendItemProps {
const ContextualLegendItem = ({ layer }: ContextualLegendItemProps) => {
const dispatch = useAppDispatch();

const { isFetching: areLayersLoading } = useContextualLayers();
const { isLoading: isLoadingData } = useContextualLayer(layer.id);

const handleOpacity = useCallback(
(opacity: number) => {
if (opacity === layer.opacity) return;
Expand Down Expand Up @@ -73,7 +68,6 @@ const ContextualLegendItem = ({ layer }: ContextualLegendItemProps) => {
isActive={layer.active}
onToggle={onToggleLayer}
id={layer.id}
isLoading={areLayersLoading || isLoadingData}
name={layer.metadata!.legend.name}
info={{
description: layer.metadata?.description,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { H3HexagonLayer } from '@deck.gl/geo-layers/typed';

import { useAppSelector } from 'store/hooks';
import { analysisMap } from 'store/features/analysis';
import { useAllContextualLayersData } from 'hooks/h3-data/contextual';
import useH3ContextualData from 'hooks/h3-data/contextual';
import DeckLayer from 'components/map/layers/deck';

import type { MapboxLayerProps, LayerProps, LayerSettings } from 'components/map/layers/types';
Expand All @@ -24,18 +24,11 @@ export const ContextualDeckLayer = ({
const { layerDeckGLProps, layers: layersMetadata } = useAppSelector(analysisMap);
const _id = id.split('-layer')[0];

const contextualData = useAllContextualLayersData();
const data = useMemo(() => {
const contextualDataById = Object.fromEntries(
contextualData
.filter((d) => {
return d.isSuccess;
})
.map(({ data: { layerId, ...rest } }) => [layerId, rest]),
);
const { data } = useH3ContextualData(_id, {
select: (d) => d.data,
keepPreviousData: true,
});

return contextualDataById[_id]?.data || [];
}, [contextualData, _id]);
const settings = useMemo(() => layerDeckGLProps[_id] || {}, [layerDeckGLProps, _id]);

const metadata = useMemo(
Expand Down
95 changes: 11 additions & 84 deletions client/src/hooks/h3-data/contextual.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useQuery, useQueries } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import chroma from 'chroma-js';

import { DEFAULT_QUERY_OPTIONS, colorScaleByLegendType } from './utils';
import { colorScaleByLegendType } from './utils';

import { apiRawService } from 'services/api';
import { analysisFilters, analysisMap } from 'store/features/analysis';
import { analysisFilters } from 'store/features/analysis';
import queryKeyStore, { type QueryKeys } from '@/lib/react-query/querykey-store';
import { useAppSelector } from 'store/hooks';
import { ErrorResponse, H3APIResponse, H3Item, Layer } from 'types';

import type { ScenarioComparisonMode } from 'store/features/analysis/scenarios';
import type { ContextualH3APIParams, ErrorResponse, H3APIResponse, H3Item, Layer } from 'types';
import type { AxiosResponse } from 'axios';
import type { UseQueryOptions, UseQueryResult, QueryFunction } from '@tanstack/react-query';
import type { UseQueryOptions } from '@tanstack/react-query';

const responseContextualParser = (response: AxiosResponse<H3APIResponse>): H3APIResponse => {
const { data, metadata } = response.data;
Expand All @@ -37,7 +37,7 @@ const useH3ContextualData = <T = H3APIResponse>(
H3APIResponse,
ErrorResponse,
T,
['h3-data-contextual', typeof id, ContextualH3APIParams]
QueryKeys['h3data']['layer']['queryKey']
>,
) => {
const filters = useAppSelector(analysisFilters);
Expand All @@ -48,8 +48,8 @@ const useH3ContextualData = <T = H3APIResponse>(
resolution: 4,
};

const query = useQuery(
['h3-data-contextual', id, params],
return useQuery(
queryKeyStore.h3data.layer(id, params).queryKey,
() =>
apiRawService
.get<H3APIResponse>(`/contextual-layers/${id}/h3data`, {
Expand All @@ -58,84 +58,11 @@ const useH3ContextualData = <T = H3APIResponse>(
// Adding color to the response
.then((response) => responseContextualParser(response)),
{
...DEFAULT_QUERY_OPTIONS,
placeholderData: {
data: [],
metadata: {
name: null,
legend: {
unit: null,
items: [],
},
},
},
...options,
enabled: (options.enabled ?? true) && !!id && !!params.year,
staleTime: 60 * 1000 * 15,
enabled: (options.enabled ?? true) && !!id && !!startYear,
},
);

return query;
};

export const useAllContextualLayersData = <T = { layerId: Layer['id'] } & H3APIResponse>(
options?: Omit<
UseQueryOptions<
{ layerId: Layer['id'] } & H3APIResponse,
ErrorResponse,
T,
[
'h3-data-contextual-all',
Layer['id'],
ContextualH3APIParams,
ScenarioComparisonMode | undefined,
]
>,
'context' | 'queryKey' | 'queryFn'
>,
) => {
const { layers } = useAppSelector(analysisMap);
const { startYear } = useAppSelector(analysisFilters);

const urlParams: Omit<ContextualH3APIParams, 'relative'> = {
year: startYear,
resolution: 4,
};

const queryList = Object.values(layers)
.filter((layer) => layer.isContextual)
.map((layer) => ({
queryKey: ['h3-data-contextual-all', layer.id, urlParams] as const,
queryFn: (({ queryKey: [, id, params] }) => {
return (
apiRawService
.get<H3APIResponse>(`/contextual-layers/${id}/h3data`, {
params,
})
// Adding color to the response
.then((response) => {
return responseContextualParser(response);
})
.then((response) => {
return { layerId: id, ...response };
})
);
}) as QueryFunction<
H3APIResponse & { layerId: Layer['id'] },
['h3-data-contextual', Layer['id'], ContextualH3APIParams]
>,
keepPreviousData: true,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
staleTime: 10 * 60 * 1000, // 10 minutes
...options,
enabled: (options?.enabled ?? true) && layer.active && !!layer.id && !!urlParams.year,
}));

const queries = useQueries({
queries: queryList,
}) as unknown[] as UseQueryResult<T, ErrorResponse>[];
return queries;
};

export default useH3ContextualData;
12 changes: 4 additions & 8 deletions client/src/hooks/h3-data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,21 @@ import { storeToQueryParams } from './utils';

import { useAppSelector } from 'store/hooks';
import { analysisFilters, scenarios } from 'store/features/analysis';
import { QueryKeys } from '@/lib/react-query/querykey-store';

import type { UseQueryOptions } from '@tanstack/react-query';
import type {
H3APIResponse,
MaterialH3APIParams,
ImpactH3APIParams,
Layer,
ContextualH3APIParams,
ErrorResponse,
} from 'types';

interface UseH3DataProps<T> {
id: Layer['id'];
params?: Partial<MaterialH3APIParams & ImpactH3APIParams>;
options?: UseQueryOptions<
H3APIResponse,
ErrorResponse,
T
// ['h3-data-contextual', string, ContextualH3APIParams]
>;
options?: UseQueryOptions<H3APIResponse, ErrorResponse, T>;
}

export const useH3Data = <T = H3APIResponse>({
Expand Down Expand Up @@ -72,9 +67,10 @@ export const useH3Data = <T = H3APIResponse>({
H3APIResponse,
ErrorResponse,
T,
['h3-data-contextual', string, ContextualH3APIParams]
QueryKeys['h3data']['layer']['queryKey']
>),
enabled: enabled && isContextual,
keepPreviousData: false,
}),
[enabled, isContextual, options],
);
Expand Down
31 changes: 0 additions & 31 deletions client/src/hooks/layers/contextual.ts

This file was deleted.

8 changes: 3 additions & 5 deletions client/src/hooks/layers/getContextualLayers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { useQuery } from '@tanstack/react-query';
import { apiRawService } from 'services/api';
import { setLayer } from 'store/features/analysis/map';
import { useAppDispatch } from 'store/hooks';
import queryKeyStore from '@/lib/react-query/querykey-store';

import type { StringifiableRecord } from 'query-string';
import type { UseQueryOptions } from '@tanstack/react-query';
import type { StringifiableRecord } from 'query-string';
import type { LayerMetadata } from 'types';

export interface ContextualLayerApiResponse {
Expand All @@ -31,16 +32,13 @@ const useContextualLayers = (
) => {
const dispatch = useAppDispatch();
return useQuery(
['contextual-layers'],
queryKeyStore.contextualLayers.categories.queryKey,
() =>
apiRawService
.get<LayerCategoriesApiResponse>('/contextual-layers/categories')
.then(({ data }) => data.data)
.then((data) => data.filter((category) => category.layers.length > 0)),
{
refetchOnMount: false,
refetchOnWindowFocus: true,
staleTime: 5 * 60 * 1000, // 5 minutes
keepPreviousData: true,
...options,
onSuccess: (data) => {
Expand Down
1 change: 1 addition & 0 deletions client/src/lib/react-query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const getQueryClient = () =>
defaultOptions: {
queries: {
staleTime: 60 * 1000,
refetchOnWindowFocus: false,
},
},
});
Expand Down
Loading

0 comments on commit ec147a0

Please sign in to comment.