Use Maptoolkit with react-map-gl and MapLibre GL JS
react-map-gl wraps MapLibre GL JS in React
components. A Maptoolkit style URL goes into its mapStyle prop, and that is the whole
integration: the style names its own tile sources and carries its own attribution.
Install
npm install react-map-gl maplibre-glreact-map-gl 8 supports both MapLibre and Mapbox, so you choose the library in the import
path. Import from react-map-gl/maplibre. The plain import Map from "react-map-gl" that
older tutorials show was removed in version 8 and fails to resolve.
@vis.gl/react-maplibre is the same MapLibre code as a separate package; react-map-gl 8
depends on it and re-exports it. Use either one, the components are identical.
The component
import "./maplibre-worker";
import Map, { NavigationControl, Marker } from "react-map-gl/maplibre";
import "maplibre-gl/dist/maplibre-gl.css";
const KEY = import.meta.env.VITE_MAPTOOLKIT_KEY;
export default function MaptoolkitMap() {
return (
<Map
initialViewState={{ longitude: 11.4041, latitude: 47.2692, zoom: 12 }}
style={{ width: "100%", height: "400px" }}
mapStyle={`https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${KEY}`}
attributionControl={{ compact: false }}
>
<NavigationControl position="top-right" />
<Marker longitude={11.4041} latitude={47.2692} />
</Map>
);
}The first import is the worker setup described in the next section.
- The API key travels in the style URL. MapLibre reuses it for every tile request the style triggers, so there is no separate tile configuration.
initialViewStateis read once. It sets the starting position and then the user is in control. To drive the position from React state instead, passlongitude,latitudeandzoomas props with anonMovehandler; that makes the map fully controlled.attributionControl={{ compact: false }}keeps the credit visible. By default MapLibre collapses the attribution into an icon as soon as the user interacts with the map, which hides the credit the license requires you to show.- The container needs a height. A map with no height renders nothing and reports no error.
The style id follows maptoolkit.<variant>, and
Standard Map Styles lists the seven variants.
MapLibre 6 needs its worker set up
With maplibre-gl 6, the map stays blank after bundling and the console shows
Worker failed to load. In the Vite development server the request for
maplibre-gl-worker.mjs returns 404.
MapLibre 6 ships its web worker as a separate file, dist/maplibre-gl-worker.mjs, and finds it
next to its own script at runtime. A bundler moves and renames the library, and that location
no longer exists. setWorkerUrl() tells MapLibre where the worker is. With Vite, the ?worker&url
import builds the worker together with the files it imports and returns its final URL:
// maplibre-worker.js
import { setWorkerUrl } from "maplibre-gl";
import workerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url";
setWorkerUrl(workerUrl);Import this module once, before the first map is created, as the component above does. The
same fix covers both the development server and the production build. With another bundler
the idea is the same: have it emit the worker file as its own bundle, then pass that file’s
URL to setWorkerUrl().
Adding your own data
Source and Layer components map one to one onto MapLibre sources and layers. They are
added once the style has loaded, so there is no load event to wait for yourself:
import Map, { Source, Layer } from "react-map-gl/maplibre";
const route = {
type: "Feature",
geometry: {
type: "LineString",
coordinates: [[11.3928, 47.2654], [11.4041, 47.2692], [11.4205, 47.2731]],
},
};
<Map initialViewState={viewState} style={{ height: "400px" }} mapStyle={styleUrl}>
<Source id="route" type="geojson" data={route}>
<Layer id="route" type="line" paint={{ "line-color": "#d6336c", "line-width": 5 }} />
</Source>
</Map>Pass new data to Source and react-map-gl updates the existing source with setData().
Calling the map directly
For anything the props do not cover, such as animated camera moves, attach a ref. The ref
exposes the camera methods directly, and getMap() returns the underlying MapLibre map:
import { useRef } from "react";
const mapRef = useRef(null);
<button onClick={() => mapRef.current?.flyTo({ center: [13.055, 47.8095], zoom: 12 })}>
Salzburg
</button>
<Map ref={mapRef} initialViewState={viewState} mapStyle={styleUrl} style={{ height: "400px" }} />react-map-gl creates and removes the MapLibre map itself, so there is no cleanup to write and no guard against React’s StrictMode double mount. With Maps JS or plain MapLibre in React, both are your job.
Next steps
The component above is a basemap with a marker. The per-API MapLibre examples show what goes
on top, and their layer definitions move into Source and Layer unchanged: a route from the
Routing API, or 3D relief from
Terrain Tiles.
Pick the style per use case before you style anything yourself. Hiking and Winter carry hillshading and read as mountain maps, and Street is built for dense city labels.