carbonplan / maps

interactive multi-dimensional data-driven web maps
https://maps.demo.carbonplan.org
Other
213 stars 18 forks source link

Help understanding how to use the `regionOptions.selector` #121

Closed Jakidxav closed 3 weeks ago

Jakidxav commented 5 months ago

Hello! I am trying to create a <Line /> chart of how raster values change over time. I know that I need access to data across time bands when the <RegionPicker /> component updates its position. My current attempt can be found here: https://github.com/Jakidxav/carbonplan_examples, where the <ParameterControls /> and <RegionControls /> components are almost identical to those in the CarbonPlan maps demo.

Currently, I have defined a 3D raster component (lat, lon, month) like so:

<Raster
  variable={'tavg'}
  source={'https://storage.googleapis.com/carbonplan-maps/v2/demo/3d/tavg-month'}
  selector={{month}}
  regionOptions={{ setData: setRegionData, selector: {month:[1, 2]} }}
  {...other props}
/>

In my modified <RegionControls /> component, I can see that the regionData prop is correctly sending Arrays of length 2 for month and tavg, with the format:

{
  value: {
  coordinates: {
    month: [1, 2],
    lat: Array(N),
    lon: Array(N),
  },
  dimensions: ['month', 'lat', 'lon'],
  tavg: {
    1: Array(N),
    2: Array(N),
  },
}

The problem is, there is an infinite loop somewhere and regionData is continually calculated and printed out once the <RegionPicker /> is open. This is the error that React sends back: Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

I believe this is coming from the queryRegion() method in the <Raster /> component's definition, but I am not sure why. It is possible that I am misunderstanding the regionOptions.selector prop, but the documentation for the region picker makes it seem like this should be doable. Is there a better way to access data across bands so that it can be used in plots every time the component is updated?

Jakidxav commented 4 months ago

I was able to get something working by modifying the handleRegionData() callback that I saw in the oae-web repo. My modified version of the hook looks like:

  const handleRegionData = useCallback(
    (data) => {
      if (data.value === null) {
        setRegionDataLoading(true)
      } else if (data.value[variable]) {
        setRegionData(data.value)
        setRegionDataLoading(false)
      }
    },
    [setRegionData, setRegionDataLoading]
  )

Then, in the <Raster /> component, I use the callback handleRegionData instead of setRegionData which is defined in the useState() call that creates regionData.

<Raster
  variable={'tavg'}
  source={...}
  selector={{month}}
  regionOptions={{ setData: handleRegionData, selector: {} }}
  {...other props}
/>

Interestingly, this only works for me when regionOptions.selector is set to an empty Object, {}. When I try to specify something like:

regionOptions={{ setData: handleRegionData, selector: {month, [1, 2, ...]} }}

I am getting the same Maximum update depth exceeded error that I got previously where regionData is continuously updated, causing the site to crash.

katamartin commented 3 weeks ago

Hey @Jakidxav! I'm able to replicate the infinite re-render loop you saw, and it looks like this is coming from the month array being redefined on every render, which is causing the infinite loop. If you move your [1, 2, ...] array out to a constant (defined outside of the component) or to state (so that the value is fixed between renders), the issue should be resolved.

Let us know if you're running into this with that fix!

Jakidxav commented 3 weeks ago

@katamartin that seems to have done the trick! Thanks for looking into this issue.