React Guide
@openweather/maps-react mirrors @openweather/maps-maplibre and @openweather/maps-ui one-for-one: a <WeatherMap>
component for the map itself, a <SimpleMapUI> for the full simple-kit demo chrome, and headless
hooks/compounds for everything in between. Importing . or ./ui never pulls in MapLibre —
only ./maplibre does.
<WeatherMap>
import {
WeatherMap,
useMapController,
useLayers,
} from '@openweather/maps-react/maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';
function App() {
return (
<WeatherMap
apiKey="YOUR_KEY"
center={[10, 50]}
zoom={3}
defaultLayer="temperature"
className="map-root"
>
{({ handle, error }) => {
if (error) return <p>{error.message}</p>;
if (!handle) return <p>Loading…</p>;
return <LayerBadge />;
}}
</WeatherMap>
);
}
function LayerBadge() {
const { active } = useLayers();
return <span>Active layer: {active}</span>;
}
<WeatherMap> mounts a createWeatherMap instance into an owned <div> and, once it resolves,
wraps its children render-prop in <MapsProviders> bound to that instance's controller — so
useMapController/useLayers/useTimeline/etc. all work inside it with zero extra wiring. Props
are UseWeatherMapOptions (everything WeatherMapOptions has except container, which
<WeatherMap> owns) plus className/style.
Only container's identity participates in the underlying effect's dependencies — changing
apiKey/center/mapStyle/etc. on a later render does not recreate the map, since
createWeatherMap only reads its options once, at creation. Force a remount (e.g. via key) if you
need a creation option to change.
<SimpleMapUI>
The whole simple-kit demo — status-text shell, layer panel, legend, overlay toggles, timeline
range control, search, zoom, point-query box — as one component, mirroring @openweather/maps-ui's
createSimpleMapUI:
import { SimpleMapUI } from '@openweather/maps-react/maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';
import '@openweather/maps-react/styles.css';
function App() {
return (
<SimpleMapUI
apiKey="YOUR_KEY"
center={[10, 50]}
zoom={3}
defaultLayer="temperature"
style={{ position: 'absolute', inset: 0 }}
/>
);
}
<SimpleMapUI> also takes a children prop, rendered inside the same <MapsProviders> tree it
sets up internally — a spot for your own components built on the ambient hooks (useLayers,
useTimeline, useSearch, useOverlays, usePointQuery, usePointFlowStore, useSettings) or
the headless compounds (Layers, Search, Timeline, LockedLayerPrompt) without standing up a
second <WeatherMap> — see examples/react/src/ApiTour.tsx in the repo for exactly this pattern.
@openweather/maps-react/styles.css re-exports @openweather/maps-ui-core's tokens.css + simple.css — the same
stylesheet the DOM renderer uses, so a <SimpleMapUI> and a createSimpleMapUI() page look
identical out of the box, and any part you override with plain CSS follows you between the two.
OpenWeather's own fuller designed UI — the old <WeatherMapUI>, its ./ui components
(LayerRail, TimelineControl, SearchControl, OverlayControls, PointQueryPopup,
DetailPanel, ChromeVisibility) and the One Call detail panel data model — now live in a separate
weather-maps website repo, built on the same headless @openweather/maps-ui-core view-models this package
still exports from ..
Composing your own controls
<SimpleMapUI> is convenient, but sometimes you want just a layer picker and a legend, in your own
layout. Every ./ui component takes the same props as its data — no bundle required. This is a
complete, runnable example: a map, a layer panel, a legend that follows the active layer, and zoom
buttons — composed by hand, without <SimpleMapUI>.
import { useState, useEffect } from 'react';
import { WeatherMap, useMapController } from '@openweather/maps-react/maplibre';
import { LayerPanel, LegendBar, ZoomControl } from '@openweather/maps-react/ui';
import type { WeatherMapHandle } from '@openweather/maps-maplibre';
import '@openweather/maps-react/styles.css';
import 'maplibre-gl/dist/maplibre-gl.css';
function App() {
return (
<WeatherMap
apiKey="YOUR_KEY"
center={[10, 50]}
zoom={3}
defaultLayer="temperature"
style={{ position: 'absolute', inset: 0 }}
>
{({ handle, error }) => {
if (error) return <p>{error.message}</p>;
if (!handle) return <p>Loading…</p>;
return <MapChrome handle={handle} />;
}}
</WeatherMap>
);
}
function MapChrome({ handle }: { handle: WeatherMapHandle }) {
const controller = useMapController();
const map = handle.getMap();
const [activeLayer, setActiveLayer] = useState(
() => controller.engine.getSnapshot().activeLayerCode,
);
// Keep the layer panel and legend in sync with real layer changes (including ones
// triggered from outside this component, e.g. `handle.setLayer()` called elsewhere).
useEffect(() => handle.on('layerchange', ({ code }) => setActiveLayer(code)), [handle]);
const metadata = controller.listLayers().find((l) => l.code === activeLayer);
return (
<div style={{ position: 'absolute', top: 12, left: 12, display: 'flex', gap: 8 }}>
<LayerPanel
available={controller.listLayers()}
activeCode={activeLayer}
onSelect={(code) => handle.setLayer(code)}
/>
<LegendBar metadata={metadata} settings={controller.settings} />
<ZoomControl onZoomIn={() => map.zoomIn()} onZoomOut={() => map.zoomOut()} />
</div>
);
}
Add <OverlayToggles>, <SearchBox>, or <PointQueryBox> the same way — each is documented under
Simple UI Kit with its own props table. Skip <Shell> entirely if you don't need a status-text
node; nothing else in the kit depends on it.
Headless: hooks over your own map
If you'd rather render your own layer picker/timeline/etc. from scratch, mount the map with
<WeatherMap> and build on useMapController() and the other hooks instead of ./ui:
import { WeatherMap } from '@openweather/maps-react/maplibre';
import {
useMapController,
useExternalStore,
useTimeline,
useTimelineScrub,
} from '@openweather/maps-react';
function App() {
return (
<WeatherMap apiKey="YOUR_KEY" defaultLayer="temperature">
{({ handle }) =>
handle && (
<>
<CustomLayerPicker />
<CustomTimeline />
</>
)
}
</WeatherMap>
);
}
function CustomLayerPicker() {
const controller = useMapController();
const { activeLayerCode } = useExternalStore(controller.engine);
return (
<ul>
{controller.listLayers().map((layer) => (
<li key={layer.code}>
<button
onClick={() => controller.setActiveLayer(layer.code)}
disabled={layer.code === activeLayerCode}
>
{layer.code}
</button>
</li>
))}
</ul>
);
}
function CustomTimeline() {
const { currentTime } = useTimeline();
const scrub = useTimelineScrub();
const hour = 60 * 60 * 1000;
return (
<div>
<button onClick={() => scrub(new Date(currentTime.getTime() - hour))}>-1h</button>
<span>{currentTime.toLocaleString()}</span>
<button onClick={() => scrub(new Date(currentTime.getTime() + hour))}>+1h</button>
</div>
);
}
See Providers & Hooks and
Headless Compounds for the full hook/compound surface, and each
page under Simple UI Kit for the styled ./ui equivalent of whatever you're rebuilding.
Core-only controller shared with a hand-rolled map
<MapsProviders> doesn't require <WeatherMap> — you can build a CoreMapController yourself
(e.g. with createCoreMapController from @openweather/maps-core, or createMapLibreController from
@openweather/maps-maplibre if you want MapLibre wiring without the rest of WeatherMapHandle) and pass it
in directly:
import { createCoreMapController } from '@openweather/maps-core';
import { MapsProviders, useLayers, useTimeline } from '@openweather/maps-react';
const controller = createCoreMapController({ apiKey: 'YOUR_KEY' });
await controller.engine.initialize();
function App() {
return (
<MapsProviders controller={controller}>
<YourOwnMapView engine={controller.engine} />
<YourOwnLayerPicker />
</MapsProviders>
);
}
This is the same controller instance a real renderer would consume — see Core-only / Custom Renderer for wiring it to a renderer of your own.