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

[Feat] Add support for latest Mapbox rendering release (V3) #2377

Open
trumbitta opened this issue Apr 15, 2024 · 5 comments
Open

[Feat] Add support for latest Mapbox rendering release (V3) #2377

trumbitta opened this issue Apr 15, 2024 · 5 comments
Assignees
Labels

Comments

@trumbitta
Copy link
Contributor

trumbitta commented Apr 15, 2024

Target Use Case

The simplest basic case with Mapbox v3 works, but as soon as you add a Source and Layer we get the Style is not done loading error.

Here's a repro: https://codesandbox.io/p/sandbox/frosty-minsky-pdqrlk?file=%2Fsrc%2FApp.js

@zsloan112
Copy link

Glad I checked here today. I was getting this same error and had been fighting it for hours.
Same use case and reproduction steps for me.

@Pessimistress Pessimistress self-assigned this Apr 15, 2024
@iostat
Copy link

iostat commented Jul 2, 2024

Been hitting this as well.

As a workaround, you can wrap everything inside your <Map...> with a component that listens for the style.load and only populates children of the <Map> once that event has been fired.

It can get a little hairy if you start doing stuff like changing the map style dynamically (totally doable, but hairy!), but something like the following can get you started.

import {useEffect, useState} from 'react';
import {default as ReactMapGL, useMap} from 'react-map-gl';

type StyleLoadedGuardProps = {
  // this state has to come from outside the StyleLoadedGuard component as otherwise
  // it'll get cleared if/when the map changes. And the guard has to be its own component
  // as it seems like that's the only way to get a ref to the map via useMap()...
  guardState: [boolean, React.Dispatch<React.SetStateAction<boolean>>]; // useState(false)
  children?: React.ReactNode;
};
const StyleLoadedGuard: React.FC<StyleLoadedGuardProps> = props => {
  const mapRef = useMap();
  const [styleLoadedAtLeastOnce, setStyleLoadedAtLeastOnce] = props.guardState;
  useEffect(() => {
    if (mapRef.current) {
      const map = mapRef.current;

      const onStyleLoad = () => {
        setStyleLoadedAtLeastOnce(true);
      };
      map.on('style.load', onStyleLoad);
      if (map.isStyleLoaded()) {
        onStyleLoad();
      }
      return () => {
        map.off('style.load', onStyleLoad);
      };
    } else {
      return undefined;
    }
  }, [mapRef]);

  return styleLoadedAtLeastOnce && props.children;
}

export const MyMap: React.FC<ReactMapGL.MapProps> = (props) => {
  const styleLoadedGuardState = useState(false);
  const mapProps = {...props, children: undefined}; // to be safe
  return <ReactMapGL.Map {...mapProps}>
    <StyleLoadedGuard guardState={styleLoadedGuardState}>
      {props.children}
    </StyleLoadedGuard>
  </ReactMapGL.Map>
}

/// should just work!
export const UseAMap: React.FC = () =>
  <MyMap>
    <ReactMapGL.Source ...>
      <ReactMapGL.Layer ... />
      <ReactMapGL.Layer ... />
      <ReactMapGL.Layer ... />
    </ReactMapGL.Source>
  </MyMap>;

I actually wanted to take a stab at making a PR that implements similar behavior on Source and Layer (there already is some similar logic for previous versions of mapbox-gl-js), but I'm not entirely sure what's the best way for me to get a dev environment going with fast-refresh and stuff.

@sepehr500
Copy link

Is there going to be a fix for this? The above won't work if you are trying to switch layers

@backuardo
Copy link

@sepehr500 - the above solution wasn't working for my app (updating mapStyle based on global state), but this seems to work (even though it's nasty).

const SafeChildren = ({ children }) => {
  const { current: map } = useMap();
  const mapStyle = useStore((state) => state.mapStyle); // My map style in Zustand
  const [canRenderChildren, setCanRenderChildren] = useState(false);

  useEffect(() => {
    if (!map || canRenderChildren) return;

    const checkStyleLoaded = () => {
      if (map.isStyleLoaded() && map.getStyle()) {
        setCanRenderChildren(true);
      }
    };

    checkStyleLoaded();
    const interval = setInterval(checkStyleLoaded, 10); // 😖

    return () => clearInterval(interval);
  }, [map, mapStyle, canRenderChildren]);

  return canRenderChildren ? <>{children}</> : null;
};

Usage:

<MapProvider>
  <Map>
    <SafeChildren>
      <Source>
        <Layer />
      </Source>
    </SafeChildren>
  </Map>
</MapProvider>

@gucr
Copy link

gucr commented Oct 7, 2024

Here is how we approached it to load layers on the fly

function RenderAfterMap({children}) {

  const map = useMap()
  const [canRender, setCanRender] = useState(false)

  useEffect(() => {
    map.current?.on('load', () => setCanRender(true))
  }, [map])

  return <>{canRender && children}</>
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants