Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor useViewportFeatures #238

Merged
merged 13 commits into from
Dec 13, 2021
33 changes: 22 additions & 11 deletions packages/react-api/src/hooks/useCartoLayerProps.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import { DataFilterExtension } from '@deck.gl/extensions';
import { _buildFeatureFilter } from '@carto/react-core';
import useViewportFeatures from './useViewportFeatures';
import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto';
import { useSelector } from 'react-redux';
import useGeoJsonFeatures from './useGeoJsonFeatures';
import useTilesetFeatures from './useTilesetFeatures';

export default function useCartoLayerProps({
source,
uniqueIdProperty,
viewportFeatures = true,
viewporFeaturesDebounceTimeout = 500
viewporFeaturesDebounceTimeout = 250
}) {
const [onViewportLoad, onDataLoad, fetch] = useViewportFeatures(
source,
const viewport = useSelector((state) => state.carto.viewport);
VictorVelarde marked this conversation as resolved.
Show resolved Hide resolved

const [onDataLoad] = useGeoJsonFeatures(source, {
viewport,
uniqueIdProperty,
debounceTimeout: viewporFeaturesDebounceTimeout
});
Clebal marked this conversation as resolved.
Show resolved Hide resolved

const [onViewportLoad, fetch] = useTilesetFeatures(source, {
viewport,
uniqueIdProperty,
viewporFeaturesDebounceTimeout
);
debounceTimeout: viewporFeaturesDebounceTimeout
});

let props = {};

Expand All @@ -23,13 +33,14 @@ export default function useCartoLayerProps({
) {
props = {
binary: true,
onViewportLoad: viewportFeatures ? onViewportLoad : null,
fetch: viewportFeatures ? fetch : null
...(viewportFeatures && {
onViewportLoad,
fetch
})
};
} else if (source?.type === MAP_TYPES.QUERY || source?.type === MAP_TYPES.TABLE) {
props = {
// empty function should be removed by null, but need a fix in CartoLayer
onDataLoad: viewportFeatures ? onDataLoad : () => null
props = viewportFeatures && {
onDataLoad
};
}

Expand Down
87 changes: 87 additions & 0 deletions packages/react-api/src/hooks/useGeoJsonFeatures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useEffect, useCallback, useState, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { setViewportFeaturesReady } from '@carto/react-redux';
import { debounce } from '@carto/react-core';
import { Methods, executeTask } from '@carto/react-workers';
import { throwError } from './utils';

export default function useGeoJsonFeatures(
source,
{ viewport, uniqueIdProperty = 'cartodb_id', debounceTimeout = 250 }
) {
const dispatch = useDispatch();
const [isGeoJsonLoaded, setGeoJsonLoaded] = useState(false);
const debounceIdRef = useRef(null);
Clebal marked this conversation as resolved.
Show resolved Hide resolved

const clearDebounce = () => {
if (debounceIdRef.current) {
clearTimeout(debounceIdRef.current);
}
debounceIdRef.current = null;
};

const sourceId = source?.id;

const setSourceViewportFeaturesReady = useCallback(
(ready) => {
if (sourceId) {
dispatch(setViewportFeaturesReady({ sourceId, ready }));
}
},
[dispatch, sourceId]
);

const computeFeaturesGeoJson = useCallback(
({ viewport, uniqueIdProperty }) => {
executeTask(sourceId, Methods.VIEWPORT_FEATURES_GEOJSON, {
viewport: Float32Array.of(...viewport),
Clebal marked this conversation as resolved.
Show resolved Hide resolved
uniqueIdProperty
})
.then(() => {
setSourceViewportFeaturesReady(true);
})
.catch(throwError);
VictorVelarde marked this conversation as resolved.
Show resolved Hide resolved
},
[setSourceViewportFeaturesReady, sourceId]
);

// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedComputeFeaturesGeoJson = useCallback(
debounce(computeFeaturesGeoJson, debounceTimeout),
[computeFeaturesGeoJson]
);

useEffect(() => {
if (sourceId && isGeoJsonLoaded) {
setSourceViewportFeaturesReady(false);
clearDebounce();
debounceIdRef.current = debouncedComputeFeaturesGeoJson({ viewport, uniqueIdProperty });
}
}, [
viewport,
uniqueIdProperty,
debouncedComputeFeaturesGeoJson,
sourceId,
isGeoJsonLoaded,
setSourceViewportFeaturesReady
]);

useEffect(() => {
if (!source) {
setGeoJsonLoaded(false);
}
}, [source]);

const onDataLoad = useCallback(
(geojson) => {
clearDebounce();
setSourceViewportFeaturesReady(false);
executeTask(sourceId, Methods.LOAD_GEOJSON_FEATURES, { geojson })
.then(() => setGeoJsonLoaded(true))
.catch(throwError);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. I don't mind a lot about then.catch, but what about the previous catch errors we had?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same answer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see the custom throw. Agree that having it with then is more compact, thus probably more readable in this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @VictorVelarde, about using await instead of then because it goes more in line with our style guides but it is a suggestion

},
[sourceId, setSourceViewportFeaturesReady]
);

return [onDataLoad];
}
130 changes: 130 additions & 0 deletions packages/react-api/src/hooks/useTilesetFeatures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { useEffect, useCallback, useState, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { setViewportFeaturesReady } from '@carto/react-redux';
import { debounce } from '@carto/react-core';
import { Methods, executeTask } from '@carto/react-workers';
import { Layer } from '@deck.gl/core';
import { throwError } from './utils';

export default function useTilesetFeatures(
source,
{ viewport, uniqueIdProperty, debounceTimeout = 250 }
) {
const dispatch = useDispatch();
const [isTilesetLoaded, setTilesetLoaded] = useState(false);
const debounceIdRef = useRef(null);

const clearDebounce = () => {
if (debounceIdRef.current) {
clearTimeout(debounceIdRef.current);
}
debounceIdRef.current = null;
};

const stopAnyCompute = useCallback(() => {
clearDebounce();
setTilesetLoaded(false);
}, [setTilesetLoaded]);

const sourceId = source?.id;

const setSourceViewportFeaturesReady = useCallback(
(ready) => {
if (sourceId) {
dispatch(setViewportFeaturesReady({ sourceId, ready }));
}
},
[dispatch, sourceId]
);

const computeViewportFeatures = useCallback(
({ viewport, uniqueIdProperty }) => {
setSourceViewportFeaturesReady(false);

executeTask(sourceId, Methods.VIEWPORT_FEATURES, {
viewport: Float32Array.of(...viewport),
uniqueIdProperty
})
.then(() => {
setSourceViewportFeaturesReady(true);
})
.catch(throwError)
.finally(clearDebounce);
},
[setSourceViewportFeaturesReady, sourceId]
);

const loadTiles = useCallback(
(tiles) => {
const cleanedTiles = tiles.reduce((acc, { data, isVisible, bbox }) => {
if (isVisible && data) {
acc.push({
data,
bbox
});
}
return acc;
}, []);

executeTask(sourceId, Methods.LOAD_TILES, { tiles: cleanedTiles })
.then(() => setTilesetLoaded(true))
.catch(throwError);
},
[sourceId]
);

// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedComputeViewportFeatures = useCallback(
debounce(computeViewportFeatures, debounceTimeout),
[computeViewportFeatures]
);

// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedLoadTiles = useCallback(debounce(loadTiles, debounceTimeout), [
loadTiles
]);

useEffect(() => {
if (sourceId && isTilesetLoaded) {
clearDebounce();
setSourceViewportFeaturesReady(false);
debounceIdRef.current = debouncedComputeViewportFeatures({
viewport,
uniqueIdProperty
});
}
}, [
viewport,
uniqueIdProperty,
debouncedComputeViewportFeatures,
sourceId,
isTilesetLoaded,
setSourceViewportFeaturesReady
]);

useEffect(() => {
if (!source) {
setTilesetLoaded(false);
}
}, [source]);

const onViewportLoad = useCallback(
(tiles) => {
stopAnyCompute();
setSourceViewportFeaturesReady(false);

debounceIdRef.current = debouncedLoadTiles(tiles);
},
[stopAnyCompute, setSourceViewportFeaturesReady, debouncedLoadTiles]
);

const fetch = useCallback(
(...args) => {
stopAnyCompute();
return Layer.defaultProps.fetch.value(...args);
},
[stopAnyCompute]
);

return [onViewportLoad, fetch];
}
Loading