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

How to detect viewport changes when viewport is changed programmatically #878

Closed
jarppe opened this issue Sep 3, 2019 · 2 comments
Closed

Comments

@jarppe
Copy link

jarppe commented Sep 3, 2019

I have attached logic to load some features from backend when onViewportChange is called. This works fine when user zoom/pan map.

When I change viewport programmatically by updating the viewport props, the map is moved and zoomed, but onViewportChange is not called.

Is there a way to detect viewport changes when viewport is moved programmatically? Or is there some better way to load features?

I'm having difficulties on loading the features in the same time I update the properties, as the features loading uses the map bounds that I get by getMap().getBounds(), but this is updated sometime after the viewport is changed.

@mayteio
Copy link

mayteio commented Oct 8, 2019

Store your viewport in state context and use an effect hook to listen to it (jump to the bottom for a note on getBounds):

// ViewportContext.js
import React, {createContext, useState} from 'react';

const ViewportState = createContext();
const ViewportUpdate = createContext();

// Yep! you can use multiple context in one Provider.
// pattern taken from:
// https://kentcdodds.com/blog/how-to-optimize-your-context-value
export function ViewportProvider({children}){
  const [viewport, setViewport] = useState();
  return (
    <ViewportState.Provider value={viewport}>
      <ViewportUpdate.Provider value={setViewport}>
        {children}
      </Viewport
    </ViewportState.Provider>
  )
}

// expose the state
export function useViewport(){
  const context = useContext(ViewportState);
  if(context === undefined) throw Error('You forgot to wrap your app in <ViewportProvider />');
  return context;
}

// expose the updater
export function useSetViewport(){
  const context = useContext(ViewportUpdate);
  if(context === undefined) throw Error('You forgot to wrap your app in <ViewportProvider />');
  return context;
}

// useViewportData.js - HERE'S WHAT YOU'RE LOOKING FOR
export function useViewportData(){
  const viewport = useViewport();
  useEffect(() => {
    // load new data when viewport changes
  }, [viewport])
}

// Example.js - programatic change of viewport. This will only
// render once! Contrived example, however, when you have complex
// UIs you don't want them rendering all the time.
export default function Example(){
  const setViewport = useSetViewport();
  const onClick = () => setViewport({...});
  return <button onClick={onClick}>Move map programmatically</button>
}

// Map.js
export default function Map(){
  // we need both the viewport and the updater in this component, though
  // that's OK because the map needs to re-render all the time anyway
  const viewport = useViewport();
  const setViewport = useSetViewport();

  // encapsulate loading login in a custom hook.
  // this can hypothetically go anywhere - but I've put it here because
  // it's coupled with a component that already renders each viewport
  // update and it shares concern with the map.
  useViewportData();

  return <ReactMapGL 
    {...viewport}
    onViewportChange={viewport => setViewport}
  />
}

// App.js - all together now
import React from 'react';
import {ViewportProvider} from './ViewportContext';
import Example from './Example';
import Map from './Map';

export default function App(){
  return (
    <ViewportProvider>
      <Example />
      <Map /> 
    </ViewportProvider>
  )
}

Notes

  1. Regarding getBounds, you may need to use extra context to store the mapRef so it's available in the useViewportData hook. When the viewport changes, call map.getBounds.

  2. This effect will run EVERY TIME the map moves, including multiple when the user pans and zooms. That seems to be OK from your description though. Use debouncing or throttling to ensure you don't call your API a zillion times a second.

  3. Any component that uses the useViewport hook will re-render just as often as the aforementioned effect will fire. Use it sparingly and always contain usage to smaller components (i.e. don't use it in a high level parent component with many children) as this will cause a render bottleneck down the chain and abuse of React.memo - Been there done that!

@jarppe
Copy link
Author

jarppe commented Oct 27, 2019

Thank you, this is just what I need. Thank you for very descriptive answer and example.

@jarppe jarppe closed this as completed Oct 27, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants