react-map-gl
react-map-gl copied to clipboard
[Feat] Add support for latest Mapbox rendering release (V3)
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
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.
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.
Is there going to be a fix for this? The above won't work if you are trying to switch layers
@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>
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}</>
}