diff --git a/web/.eslintrc.json b/web/.eslintrc.json index d49276ba2f..fb7cd6374a 100644 --- a/web/.eslintrc.json +++ b/web/.eslintrc.json @@ -16,6 +16,7 @@ "plugin:jsx-a11y/recommended", "plugin:react/jsx-runtime", "plugin:unicorn/recommended", + "plugin:react-hooks/recommended", "plugin:react-prefer-function-component/recommended", "prettier" ], diff --git a/web/src/api/getWeatherData.ts b/web/src/api/getWeatherData.ts index 9426d3fdef..aaddfe3fea 100644 --- a/web/src/api/getWeatherData.ts +++ b/web/src/api/getWeatherData.ts @@ -1,6 +1,6 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query'; import { add, startOfHour, sub } from 'date-fns'; -import { useInterpolatedData } from 'features/weather-layers/hooks'; +import { getInterpolatedData } from 'features/weather-layers/weatherUtils'; import type { Maybe } from 'types'; import { FIVE_MINUTES, getBasePath, getHeaders } from './helpers'; @@ -101,7 +101,7 @@ async function getWeatherData(type: WeatherType) { const forecasts = await Promise.all([before, after]).then((values) => { return values; }); - const interdata = useInterpolatedData(type, forecasts); + const interdata = getInterpolatedData(type, forecasts); return interdata; } diff --git a/web/src/api/getZone.ts b/web/src/api/getZone.ts index ddbf17340a..683f19415e 100644 --- a/web/src/api/getZone.ts +++ b/web/src/api/getZone.ts @@ -4,7 +4,7 @@ import { useAtom } from 'jotai'; import invariant from 'tiny-invariant'; import type { ZoneDetails } from 'types'; import { TimeAverages } from 'utils/constants'; -import { getZoneFromPath } from 'utils/helpers'; +import { useGetZoneFromPath } from 'utils/helpers'; import { timeAverageAtom } from 'utils/state/atoms'; import { cacheBuster, getBasePath, getHeaders, QUERY_KEYS } from './helpers'; @@ -38,7 +38,7 @@ const getZone = async ( // TODO: The frontend (graphs) expects that the datetimes in state are the same as in zone // should we add a check for this? const useGetZone = (): UseQueryResult => { - const zoneId = getZoneFromPath(); + const zoneId = useGetZoneFromPath(); const [timeAverage] = useAtom(timeAverageAtom); return useQuery( [QUERY_KEYS.ZONE, { zone: zoneId, aggregate: timeAverage }], diff --git a/web/src/components/Accordion.tsx b/web/src/components/Accordion.tsx index 7cf86a1c87..511134bffb 100644 --- a/web/src/components/Accordion.tsx +++ b/web/src/components/Accordion.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/rules-of-hooks */ import { PrimitiveAtom, useAtom } from 'jotai'; import { useEffect, useState } from 'react'; import { HiChevronDown, HiChevronUp } from 'react-icons/hi2'; diff --git a/web/src/components/LoadingOverlay.tsx b/web/src/components/LoadingOverlay.tsx index 85d0e45a9f..ea1670be0b 100644 --- a/web/src/components/LoadingOverlay.tsx +++ b/web/src/components/LoadingOverlay.tsx @@ -31,7 +31,7 @@ function FadingOverlay({ isVisible }: { isVisible: boolean }) { clearTimeout(timeoutId); setShowButton(false); }; - }, [isVisible]); + }, [isVisible, showButton]); return transitions( (styles, isVisible) => diff --git a/web/src/components/TimeSlider.tsx b/web/src/components/TimeSlider.tsx index 53f539fe48..a9e0adb285 100644 --- a/web/src/components/TimeSlider.tsx +++ b/web/src/components/TimeSlider.tsx @@ -5,7 +5,7 @@ import { useDarkMode } from 'hooks/theme'; import { useAtom } from 'jotai/react'; import trackEvent from 'utils/analytics'; import { TimeAverages } from 'utils/constants'; -import { getZoneFromPath } from 'utils/helpers'; +import { useGetZoneFromPath } from 'utils/helpers'; import { timeAverageAtom } from 'utils/state/atoms'; type NightTimeSet = number[]; @@ -148,7 +148,7 @@ export function TimeSliderWithNight(props: TimeSliderProps) { } function TimeSlider(props: TimeSliderProps) { - const zoneId = getZoneFromPath(); + const zoneId = useGetZoneFromPath(); const [timeAverage] = useAtom(timeAverageAtom); const showNightTime = zoneId && timeAverage === TimeAverages.HOURLY; diff --git a/web/src/components/tooltips/TooltipWrapper.tsx b/web/src/components/tooltips/TooltipWrapper.tsx index 496a6f1e26..a156cb7c87 100644 --- a/web/src/components/tooltips/TooltipWrapper.tsx +++ b/web/src/components/tooltips/TooltipWrapper.tsx @@ -21,10 +21,11 @@ export default function TooltipWrapper({ tooltipClassName, isMobile, }: TooltipWrapperProperties): ReactElement { + const [isOpen, setIsOpen] = useState(false); + if (!tooltipContent) { return children; } - const [isOpen, setIsOpen] = useState(false); // Helpers const openTooltip = () => setIsOpen(true); diff --git a/web/src/features/charts/bar-breakdown/BarBreakdownEmissionsChart.tsx b/web/src/features/charts/bar-breakdown/BarBreakdownEmissionsChart.tsx index 3bdeea5dcc..20aca982f7 100644 --- a/web/src/features/charts/bar-breakdown/BarBreakdownEmissionsChart.tsx +++ b/web/src/features/charts/bar-breakdown/BarBreakdownEmissionsChart.tsx @@ -7,7 +7,7 @@ import { ElectricityModeType, ZoneDetail, ZoneKey } from 'types'; import { modeColor } from 'utils/constants'; import { formatCo2 } from 'utils/formatting'; -import { LABEL_MAX_WIDTH, PADDING_X, PADDING_Y } from './constants'; +import { LABEL_MAX_WIDTH, PADDING_X } from './constants'; import Axis from './elements/Axis'; import HorizontalBar from './elements/HorizontalBar'; import Row from './elements/Row'; diff --git a/web/src/features/charts/elements/AreaGraph.tsx b/web/src/features/charts/elements/AreaGraph.tsx index c3c9aad056..194b3316cc 100644 --- a/web/src/features/charts/elements/AreaGraph.tsx +++ b/web/src/features/charts/elements/AreaGraph.tsx @@ -11,7 +11,7 @@ import { TimeAverages } from 'utils/constants'; import { selectedDatetimeIndexAtom } from 'utils/state/atoms'; import { useBreakpoint } from 'utils/styling'; -import { getTimeScale, isEmpty } from '../graphUtils'; +import { getTimeScale } from '../graphUtils'; import AreaGraphTooltip from '../tooltips/AreaGraphTooltip'; import { AreaGraphElement, FillFunction, InnerAreaGraphTooltipProps } from '../types'; import AreaGraphLayers from './AreaGraphLayers'; @@ -140,26 +140,27 @@ function AreaGraph({ () => getValueScale(containerHeight, totalValues), [containerHeight, totalValues] ); - const startTime = datetimes?.at(0); - const lastTime = datetimes?.at(-1); - const interval = datetimes?.at(-2); + const startTime = datetimes.at(0); + const lastTime = datetimes.at(-1); + const interval = datetimes.at(-2); - if (!startTime || !lastTime || !interval) { - return null; - } const intervalMs = datetimes.length > 1 && interval && lastTime ? lastTime.getTime() - interval.getTime() : 0; // The endTime needs to include the last interval so it can be shown const endTime = useMemo( - () => new Date(lastTime.getTime() + intervalMs), + () => (lastTime ? new Date(lastTime.getTime() + intervalMs) : null), [lastTime, intervalMs] ); - const datetimesWithNext = useMemo(() => [...datetimes, endTime], [datetimes, endTime]); - if (!endTime) { - return null; - } + + const datetimesWithNext = useMemo( + // The as Date[] assertion is needed because the filter removes the null values but typescript can't infer that + // This can be inferred by typescript 5.5 and can be removed when we upgrade + () => [...datetimes, endTime].filter(Boolean) as Date[], + [datetimes, endTime] + ); + const timeScale = useMemo( () => getTimeScale(containerWidth, startTime, endTime), [containerWidth, startTime, endTime] @@ -209,12 +210,6 @@ function AreaGraph({ [setGraphIndex, setSelectedLayerIndex] ); - // Don't render the graph at all if no layers are present - if (isEmpty(layers)) { - console.error('No layers present in AreaGraph'); - return null; - } - // Don't render the graph if datetimes and datapoints are not in sync for (const layer of layers) { if (layer.datapoints.length !== datetimes.length) { diff --git a/web/src/features/charts/graphUtils.ts b/web/src/features/charts/graphUtils.ts index c51712becf..b89c496ba8 100644 --- a/web/src/features/charts/graphUtils.ts +++ b/web/src/features/charts/graphUtils.ts @@ -44,17 +44,20 @@ export const detectHoveredDatapointIndex = ( export const getTooltipPosition = (isMobile: boolean, marker: { x: number; y: number }) => isMobile ? { x: 0, y: 0 } : marker; -// TODO: Deprecate this -export const isEmpty = (object: any) => - [Object, Array].includes((object || {}).constructor) && - Object.entries(object || {}).length === 0; - export const noop = () => undefined; -export const getTimeScale = (width: number, startTime: Date, endTime: Date) => - scaleTime() +export const getTimeScale = ( + width: number, + startTime?: Date | null, + endTime?: Date | null +) => { + if (!startTime || !endTime) { + return null; + } + return scaleTime() .domain([new Date(startTime), new Date(endTime)]) .range([0, width]); +}; export const getStorageKey = (name: ElectricityStorageType): string | undefined => { switch (name) { diff --git a/web/src/features/charts/hooks/useNetExchangeChartData.ts b/web/src/features/charts/hooks/useNetExchangeChartData.ts index fbc7060169..a04aca5096 100644 --- a/web/src/features/charts/hooks/useNetExchangeChartData.ts +++ b/web/src/features/charts/hooks/useNetExchangeChartData.ts @@ -31,11 +31,13 @@ export function getFills(data: AreaGraphElement[]) { export function useNetExchangeChartData() { const { data: zoneData, isLoading, isError } = useGetZone(); + const [displayByEmissions] = useAtom(displayByEmissionsAtom); + const [timeAggregate] = useAtom(timeAverageAtom); + if (isLoading || isError) { return { isLoading, isError }; } - const [displayByEmissions] = useAtom(displayByEmissionsAtom); - const [timeAggregate] = useAtom(timeAverageAtom); + const { valueFactor, valueAxisLabel } = getValuesInfo( Object.values(zoneData.zoneStates), displayByEmissions, diff --git a/web/src/features/charts/tooltips/NetExchangeChartTooltip.tsx b/web/src/features/charts/tooltips/NetExchangeChartTooltip.tsx index 441883ad0c..0d0c293763 100644 --- a/web/src/features/charts/tooltips/NetExchangeChartTooltip.tsx +++ b/web/src/features/charts/tooltips/NetExchangeChartTooltip.tsx @@ -11,13 +11,14 @@ import AreaGraphToolTipHeader from './AreaGraphTooltipHeader'; export default function NetExchangeChartTooltip({ zoneDetail, }: InnerAreaGraphTooltipProps) { - if (!zoneDetail) { - return null; - } const [timeAverage] = useAtom(timeAverageAtom); const [displayByEmissions] = useAtom(displayByEmissionsAtom); const { t } = useTranslation(); + if (!zoneDetail) { + return null; + } + const isHourly = timeAverage === TimeAverages.HOURLY; const { stateDatetime } = zoneDetail; diff --git a/web/src/features/exchanges/ExchangeArrow.tsx b/web/src/features/exchanges/ExchangeArrow.tsx index c1196aa380..b4c5c85d81 100644 --- a/web/src/features/exchanges/ExchangeArrow.tsx +++ b/web/src/features/exchanges/ExchangeArrow.tsx @@ -26,15 +26,28 @@ function ExchangeArrow({ isMobile, }: ExchangeArrowProps) { const { co2intensity, lonlat, netFlow, rotation, key } = data; - if (!lonlat) { - return null; - } + + const setIsMoving = useSetAtom(mapMovingAtom); + + useEffect(() => { + const cancelWheel = (event: Event) => event.preventDefault(); + const exchangeLayer = document.querySelector('#exchange-layer'); + if (!exchangeLayer) { + return; + } + exchangeLayer.addEventListener('wheel', cancelWheel, { + passive: true, + }); + return () => exchangeLayer.removeEventListener('wheel', cancelWheel); + }, []); const absFlow = Math.abs(netFlow ?? 0); - // Don't render if the flow is very low ... - if (absFlow < 1) { + + // Don't render if there is no position or if flow is very low ... + if (!lonlat || absFlow < 1) { return null; } + const mapZoom = map.getZoom(); const projection = map.project(lonlat); const transform = { @@ -56,20 +69,6 @@ function ExchangeArrow({ return null; } - const setIsMoving = useSetAtom(mapMovingAtom); - - useEffect(() => { - const cancelWheel = (event: Event) => event.preventDefault(); - const exchangeLayer = document.querySelector('#exchange-layer'); - if (!exchangeLayer) { - return; - } - exchangeLayer.addEventListener('wheel', cancelWheel, { - passive: true, - }); - return () => exchangeLayer.removeEventListener('wheel', cancelWheel); - }, []); - const prefix = colorBlindMode ? 'colorblind-' : ''; const intensity = quantizedCo2IntensityScale(co2intensity); const speed = quantizedExchangeSpeedScale(absFlow); diff --git a/web/src/features/map/Map.tsx b/web/src/features/map/Map.tsx index 5ae2f46750..e78f728936 100644 --- a/web/src/features/map/Map.tsx +++ b/web/src/features/map/Map.tsx @@ -145,6 +145,10 @@ export default function MapPage({ onMapLoad }: MapPageProps): ReactElement { isSourceLoaded, spatialAggregate, isSuccess, + isLoading, + isError, + worldGeometries.features, + theme.clickableFill, ]); useEffect(() => { @@ -156,7 +160,7 @@ export default function MapPage({ onMapLoad }: MapPageProps): ReactElement { map.flyTo({ center: [data.callerLocation[0], data.callerLocation[1]] }); setIsFirstLoad(false); } - }, [map, isSuccess]); + }, [map, isSuccess, isError, isFirstLoad, data?.callerLocation, selectedZoneId]); useEffect(() => { // Run when the selected zone changes @@ -186,7 +190,15 @@ export default function MapPage({ onMapLoad }: MapPageProps): ReactElement { map.flyTo({ center: isMobile ? center : centerMinusLeftPanelWidth, zoom: 3.5 }); } } - }, [map, location.pathname, isLoadingMap]); + }, [ + map, + location.pathname, + isLoadingMap, + selectedZoneId, + setHoveredZone, + worldGeometries.features, + setLeftPanelOpen, + ]); const onClick = (event: maplibregl.MapLayerMouseEvent) => { if (!map || !event.features) { diff --git a/web/src/features/map/MapWrapper.tsx b/web/src/features/map/MapWrapper.tsx index ffd97c0f50..031f9a9c00 100644 --- a/web/src/features/map/MapWrapper.tsx +++ b/web/src/features/map/MapWrapper.tsx @@ -15,7 +15,7 @@ export default function MapWrapper() { if (shouldShowFallback) { setIsLoadingMap(false); } - }, [shouldShowFallback]); + }, [setIsLoadingMap, shouldShowFallback]); return ( <> diff --git a/web/src/features/panels/zone/EstimationCard.tsx b/web/src/features/panels/zone/EstimationCard.tsx index e8a1a96155..a23c0fa648 100644 --- a/web/src/features/panels/zone/EstimationCard.tsx +++ b/web/src/features/panels/zone/EstimationCard.tsx @@ -25,7 +25,7 @@ export default function EstimationCard({ estimatedPercentage?: number; zoneMessage?: ZoneMessage; }) { - const [isFeedbackCardVisibile, setIsFeedbackCardVisibile] = useState(false); + const [isFeedbackCardVisible, setIsFeedbackCardVisible] = useState(false); const [feedbackCardCollapsedNumber, _] = useAtom(feedbackCardCollapsedNumberAtom); const feedbackEnabled = useFeatureFlag('feedback-estimation-labels'); const [hasFeedbackCardBeenSeen, setHasFeedbackCardBeenSeen] = useAtom( @@ -33,16 +33,22 @@ export default function EstimationCard({ ); useEffect(() => { - setIsFeedbackCardVisibile( + setIsFeedbackCardVisible( feedbackEnabled && showEstimationFeedbackCard( feedbackCardCollapsedNumber, - isFeedbackCardVisibile, + isFeedbackCardVisible, hasFeedbackCardBeenSeen, setHasFeedbackCardBeenSeen ) ); - }, [feedbackEnabled, feedbackCardCollapsedNumber]); + }, [ + feedbackEnabled, + feedbackCardCollapsedNumber, + isFeedbackCardVisible, + hasFeedbackCardBeenSeen, + setHasFeedbackCardBeenSeen, + ]); switch (cardType) { case 'outage': { @@ -55,14 +61,14 @@ export default function EstimationCard({ return (
- {isFeedbackCardVisibile && } + {isFeedbackCardVisible && }
); } } } -function getEstimationTranslation( +function useGetEstimationTranslation( field: 'title' | 'pill' | 'body', estimationMethod?: string, estimatedPercentage?: number @@ -117,13 +123,13 @@ function BaseCard({ }; const { t } = useTranslation(); - const title = getEstimationTranslation('title', estimationMethod); - const pillText = getEstimationTranslation( + const title = useGetEstimationTranslation('title', estimationMethod); + const pillText = useGetEstimationTranslation( 'pill', estimationMethod, estimatedPercentage ); - const bodyText = getEstimationTranslation( + const bodyText = useGetEstimationTranslation( 'body', estimationMethod, estimatedPercentage @@ -247,10 +253,12 @@ function truncateString(string_: string, number_: number) { } function ZoneMessageBlock({ zoneMessage }: { zoneMessage?: ZoneMessage }) { + const { t } = useTranslation(); + if (!zoneMessage || !zoneMessage.message) { return null; } - const { t } = useTranslation(); + return ( {truncateString(zoneMessage.message, 300)}{' '} diff --git a/web/src/features/panels/zone/FeedbackCard.tsx b/web/src/features/panels/zone/FeedbackCard.tsx index 3463cdb8a8..80f9fbce91 100644 --- a/web/src/features/panels/zone/FeedbackCard.tsx +++ b/web/src/features/panels/zone/FeedbackCard.tsx @@ -22,8 +22,8 @@ export default function FeedbackCard({ setIsClosed(true); }; - const title = getQuestionTranslation('title', feedbackState); - const subtitle = getQuestionTranslation('subtitle', feedbackState); + const title = useGetQuestionTranslation('title', feedbackState); + const subtitle = useGetQuestionTranslation('subtitle', feedbackState); if (isClosed) { return null; @@ -81,9 +81,9 @@ function InputField({ inputText: string; handleInputChange: (event: { target: { value: SetStateAction } }) => void; }) { - const inputPlaceholder = getQuestionTranslation('placeholder'); - const optional = getQuestionTranslation('optional'); - const text = getQuestionTranslation('input-question'); + const inputPlaceholder = useGetQuestionTranslation('placeholder'); + const optional = useGetQuestionTranslation('optional'); + const text = useGetQuestionTranslation('input-question'); return (
@@ -114,7 +114,7 @@ function InputField({ } function SubmitButton({ handleSave }: { handleSave: () => void }) { - const buttonText = getQuestionTranslation('submit'); + const buttonText = useGetQuestionTranslation('submit'); return ; } @@ -129,9 +129,9 @@ function FeedbackActions({ estimationMethod?: string; }) { const [inputText, setInputText] = useState(''); - const [feedbackScore, setfeedbackScore] = useState(''); + const [feedbackScore, setFeedbackScore] = useState(''); - const question = getQuestionTranslation('rate-question'); + const question = useGetQuestionTranslation('rate-question'); const handleInputChange = (event: { target: { value: SetStateAction } }) => { setInputText(event.target.value); @@ -160,7 +160,7 @@ function FeedbackActions({
{feedbackState === FeedbackState.OPTIONAL && (
@@ -182,9 +182,9 @@ function ActionPills({ setFeedbackState: Dispatch>; setFeedbackScore: Dispatch>; }) { - const agreeText = getQuestionTranslation('agree'); + const agreeText = useGetQuestionTranslation('agree'); const [pillContent] = useState(['1', '2', '3', '4', '5']); - const disagreeText = getQuestionTranslation('disagree'); + const disagreeText = useGetQuestionTranslation('disagree'); const [currentPillNumber, setPillNumber] = useState(''); const handlePillClick = (identifier: string) => { @@ -257,7 +257,7 @@ function PillContent({ ); } -function getQuestionTranslation(field: string, feedbackState?: FeedbackState) { +function useGetQuestionTranslation(field: string, feedbackState?: FeedbackState) { const { t } = useTranslation(); if (feedbackState != undefined) { if ( diff --git a/web/src/features/panels/zone/ZoneDetails.tsx b/web/src/features/panels/zone/ZoneDetails.tsx index 00012deff3..0be01e8063 100644 --- a/web/src/features/panels/zone/ZoneDetails.tsx +++ b/web/src/features/panels/zone/ZoneDetails.tsx @@ -28,22 +28,16 @@ import ZoneHeaderTitle from './ZoneHeaderTitle'; export default function ZoneDetails(): JSX.Element { const { zoneId } = useParams(); - if (!zoneId) { - return ; - } const [timeAverage] = useAtom(timeAverageAtom); const [displayByEmissions] = useAtom(displayByEmissionsAtom); const [_, setViewMode] = useAtom(spatialAggregateAtom); const [selectedDatetime] = useAtom(selectedDatetimeIndexAtom); - const hasSubZones = getHasSubZones(zoneId); - const isSubZone = zoneId ? zoneId.includes('-') : true; const { data, isError, isLoading } = useGetZone(); const { t } = useTranslation(); - // TODO: App-backend should not return an empty array as "data" if the zone does not - // exist. - if (Array.isArray(data)) { - return ; - } + const isMobile = !useBreakpoint('sm'); + + const hasSubZones = getHasSubZones(zoneId); + const isSubZone = zoneId ? zoneId.includes('-') : true; useEffect(() => { if (hasSubZones === null) { @@ -57,7 +51,17 @@ export default function ZoneDetails(): JSX.Element { if (!hasSubZones && isSubZone) { setViewMode(SpatialAggregate.ZONE); } - }, []); + }, [hasSubZones, isSubZone, setViewMode]); + + if (!zoneId) { + return ; + } + + // TODO: App-backend should not return an empty array as "data" if the zone does not + // exist. + if (Array.isArray(data)) { + return ; + } const zoneDataStatus = getZoneDataStatus(zoneId, data, timeAverage); @@ -69,8 +73,6 @@ export default function ZoneDetails(): JSX.Element { const cardType = getCardType({ estimationMethod, zoneMessage, timeAverage }); const hasEstimationPill = Boolean(estimationMethod) || Boolean(estimatedPercentage); - const isMobile = !useBreakpoint('sm'); - return ( <> diff --git a/web/src/features/time/TimeController.tsx b/web/src/features/time/TimeController.tsx index 159db24f13..cd3c1729cf 100644 --- a/web/src/features/time/TimeController.tsx +++ b/web/src/features/time/TimeController.tsx @@ -39,7 +39,7 @@ export default function TimeController({ className }: { className?: string }) { index: datetimes.length - 1, }); } - }, [data]); + }, [data, datetimes, setSelectedDatetime]); const onTimeSliderChange = (index: number) => { // TODO: Does this work properly missing values? diff --git a/web/src/features/weather-layers/solar/SolarLayer.tsx b/web/src/features/weather-layers/solar/SolarLayer.tsx index 2f7fdc9da2..d9544da44d 100644 --- a/web/src/features/weather-layers/solar/SolarLayer.tsx +++ b/web/src/features/weather-layers/solar/SolarLayer.tsx @@ -93,7 +93,7 @@ export default function SolarLayer({ map }: { map?: maplibregl.Map }) { map.removeSource('solar'); } }; - }, [map, node, isVisibleReference.current]); + }, [map, node, setIsLoadingSolarLayer, isVisibleReference.current]); // Render the processed solar forecast image into the canvas. useEffect(() => { diff --git a/web/src/features/weather-layers/hooks.ts b/web/src/features/weather-layers/weatherUtils.ts similarity index 97% rename from web/src/features/weather-layers/hooks.ts rename to web/src/features/weather-layers/weatherUtils.ts index 3e2b4f1217..9db441be80 100644 --- a/web/src/features/weather-layers/hooks.ts +++ b/web/src/features/weather-layers/weatherUtils.ts @@ -5,7 +5,7 @@ import { Maybe } from 'types'; import { getReferenceTime, getTargetTime } from './grib'; -export function useInterpolatedData( +export function getInterpolatedData( type: WeatherType, rawData: GfsForecastResponse[] ): Maybe { diff --git a/web/src/features/weather-layers/wind-layer/WindLayer.tsx b/web/src/features/weather-layers/wind-layer/WindLayer.tsx index 5347178692..8f68b703fb 100644 --- a/web/src/features/weather-layers/wind-layer/WindLayer.tsx +++ b/web/src/features/weather-layers/wind-layer/WindLayer.tsx @@ -75,14 +75,23 @@ export default function WindLayer({ map }: { map?: maplibregl.Map }) { windy.stop(); setWindy(null); } - }, [isVisible, isSuccess, reference.current, windy]); + }, [ + isVisible, + isSuccess, + windy, + map, + isWindLayerEnabled, + windData, + setIsLoadingWindLayer, + viewport, + ]); useEffect(() => { if (windy) { const { bounds, width, height } = viewport; windy.start(bounds, width, height); } - }, [viewport]); + }, [viewport, windy]); return ( (undefined); useEffect(() => { @@ -46,7 +46,7 @@ export function useNightTimes() { setNightTimes(undefined); } } - }, [zoneId, data]); + }, [zoneId, data, worldGeometries.features]); return nightTimes; } diff --git a/web/src/utils/formatting.test.ts b/web/src/utils/formatting.test.ts index f1547bdcfd..da66e7f3a8 100644 --- a/web/src/utils/formatting.test.ts +++ b/web/src/utils/formatting.test.ts @@ -1,4 +1,4 @@ -import { formatCo2, formatDataSources, formatEnergy, formatPower } from './formatting'; +import { formatCo2, formatEnergy, formatPower } from './formatting'; describe('formatEnergy', () => { it('handles NaN input', () => { diff --git a/web/src/utils/helpers.ts b/web/src/utils/helpers.ts index 7dba4a79d3..696bcc942f 100644 --- a/web/src/utils/helpers.ts +++ b/web/src/utils/helpers.ts @@ -7,12 +7,12 @@ import { ZoneDetail, } from 'types'; -export function getZoneFromPath() { +export function useGetZoneFromPath() { const { zoneId } = useParams(); + const match = useMatch('/zone/:id'); if (zoneId) { return zoneId; } - const match = useMatch('/zone/:id'); return match?.params.id || undefined; }