OpenWeather Maps SDK v0.0.0 docs
Guides

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.