npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@trackunit/react-map

v0.1.33

Published

Provider-agnostic, hook-first map library for React applications. Switch between Google Maps and Mapbox without touching your application code.

Readme

@trackunit/react-map

Provider-agnostic, hook-first map library for React applications. Switch between Google Maps and Mapbox without touching your application code.

Design principles

  • Provider behind an adapter. Google Maps or Mapbox is passed in as adapter config. Swapping providers is a one-line change; your components, events, and tests are untouched.
  • Hook-first, TanStack-style. State, actions, and the renderable component are co-located at the call site via useMap(adapter). No context tree to set up; no provider-specific types leaking into your code.
  • Resolver props — you decide. Where choices are genuinely the consumer's — which markers render as DOM vs symbol, what style a hovered shape gets — the library hands you a fully-described context object and takes a small decision back. It never makes the heuristic call behind your back.
  • Defaults, but always composable. Every resolver ships a sensible default that is exported for composition. Use it as-is, wrap it, or replace it entirely.
  • Atomic components. MapMarker, ClusterMarker, MapMarkerIcon, and Panel are usable stand-alone, even outside a map.
  • Strong types. Discriminated unions, exhaustive switches, and typed resolver contracts catch mistakes at compile time.
  • No vendor leakage. All provider-specific types and APIs belong inside adapters. Nothing outside an adapter may import from mapbox-gl or @vis.gl/react-google-maps.
  • Self-contained, portable layers. A well-designed layer hook owns its own data loading, controls, and settings so it can be dropped into any map without surrounding setup. Concretely, layers should accept a pre-built filters object as a prop rather than fetching raw data — the hook owns the query internally and the caller owns the filter shape.

Three layers of abstraction

The library is intentionally split into three tiers. Reach for the highest-level tier that satisfies your needs, and only drop down a layer when you need more control. Starting at a lower tier than necessary means reinventing abstractions the higher tiers already provide.

Tier 1 — useMap and MapApi

The foundation. Full control over camera, events, controls, panels, annotations, and the adapter surface. Maximum flexibility; you invent everything yourself.

Use this when you are building something structurally novel or need to escape a constraint in a higher-level hook.

Tier 2 — Layer hooks (useMarkers, useShapes, …)

The everyday workhorse. Layer hooks give a high-productivity path to common map features without giving anything away. They surface full library behaviour with sensible defaults you can override one decision at a time via resolver props.

The atomic building blocks these compose are exported for reuse too: the output components (MapMarker, ClusterMarker, MapMarkerIcon) and helper hooks (useAdaptiveMarkerHelpers, useShapeLabelHelpers, …) that surface the default resolver heuristics. This is where almost all map feature work should live.

Tier 3 — Your domain layer hooks

The top tier is your own domain hooks — built on top of Tier 2 primitives. Highly opinionated hooks that own data fetching, display logic, controls, and interaction end-to-end. Closed by design — no customization knobs. If you need something similar but with different behaviour, drop to Tier 2 and compose from the atomic pieces.

Every finished, map-ready layer is a Tier 3 hook, so the tier you pick is really your starting point: use a ready-made domain hook as-is, or assemble your own from Tier 2 primitives.

This library exports generic primitives only. Domain hooks belong in the domain library that serves them — not in @trackunit/react-map. Trackunit's own asset and site layer hooks are reference implementations that will be published separately. If you build a domain layer that would be valuable beyond your own app, consider publishing it as its own package too.

Installation

npm install @trackunit/react-map

Map providers live in separate adapter packages — install whichever one(s) you use alongside it:

# Google Maps
npm install @trackunit/react-map-adapter-google

# Mapbox
npm install @trackunit/react-map-adapter-mapbox

Always import providers from the adapter package, never from @trackunit/react-map:

  • Google Maps: @trackunit/react-map-adapter-google (googleMapsAdapter, GoogleApiProvider, …)
  • Mapbox: @trackunit/react-map-adapter-mapbox (mapboxAdapter, …)

Usage

Google Maps

import { useCameraState, useMap } from "@trackunit/react-map";
import { googleMapsAdapter } from "@trackunit/react-map-adapter-google";

const MyMap = () => {
  const [Map, api] = useMap(googleMapsAdapter({
    apiKey: process.env.GOOGLE_MAPS_API_KEY,
    theme: "light",
    language: "en",
  }));

  const { center, zoom } = useCameraState(api);

  return (
    <div className="relative h-full">
      <p>Center: {center[0]}, {center[1]}</p>
      <p>Zoom: {zoom}</p>
      <Map className="h-full w-full">
        {/* Children render inside the map */}
      </Map>
    </div>
  );
};

Mapbox

import { useCameraState, useMap } from "@trackunit/react-map";
import { mapboxAdapter } from "@trackunit/react-map-adapter-mapbox";

const MyMap = () => {
  const [Map, api] = useMap(mapboxAdapter({
    accessToken: process.env.MAPBOX_ACCESS_TOKEN,
    theme: "dark",
    language: "en",
  }));

  const { center, zoom } = useCameraState(api);

  return (
    <div className="relative h-full">
      <p>Center: {center[0]}, {center[1]}</p>
      <p>Zoom: {zoom}</p>
      <Map className="h-full w-full">
        {/* Same children work with both Google and Mapbox! */}
      </Map>
    </div>
  );
};

API

useMap(adapter)

Returns a tuple [Map, api]:

  • Map - React component to render the map
  • api.state - Map status (isReady, initializationFailed, appearance, rasterResolution)
  • useCameraState(api) - Reactive camera state (center, zoom, bounds, isIdle — changes at up to 60fps)
  • api.actions.setCenter(position) - Set map center (returns promise)
  • api.actions.setZoom(zoom) - Set zoom level (returns promise)
  • api.actions.zoomBy(delta) - Adjust zoom by delta (returns promise)
  • api.actions.fitBounds(bounds, options) - Fit map to bounds (returns promise)
  • api.actions.panTo(position) - Pan to position with animation (returns promise)
  • api.on(event, handler) - Subscribe to map events (returns unsubscribe function)

Coordinates

All coordinates use GeoJSON format: [longitude, latitude]

const berlin: GeoJsonPosition = [13.405, 52.52]; // [lng, lat]

Bounding Boxes

All bounding boxes use GeoJSON format: [minLng, minLat, maxLng, maxLat]

const europeBounds: GeoJsonBbox = [-10, 35, 40, 70];

Map Actions

All actions return promises that resolve when the map finishes moving:

await api.actions.setCenter([13.405, 52.52]);
await api.actions.setZoom(12);
await api.actions.fitBounds([-10, 35, 40, 70], { padding: 50, maxZoom: 15 });

Map State

api.state contains low-frequency MapStatus (readiness, initialization failure, appearance, raster resolution). For CameraState that changes during panning/zooming, use useCameraState(api):

// Initialization state — rarely changes
const { isReady, appearance } = api.state;

// Camera state — changes at up to 60fps during panning
const { center, zoom, bounds, isIdle } = useCameraState(api);

// Only re-renders when zoom changes (opt-in)
const { zoom } = useCameraState(api);
useEffect(() => {
  console.log("Zoom changed:", zoom);
}, [zoom]);

Map Events

Subscribe to map events with type-safe handlers:

useEffect(() => {
  const unsubscribe = api.on("click", (e) => {
    console.log("Clicked at:", e.position);
  });

  return unsubscribe;
}, [api.on]);

Available events:

  • idle - Map finished moving
  • movestart - Map started moving
  • moveend - Map finished moving (includes final state)
  • click - Map clicked (includes position and original event)

Switching Providers

To switch providers, only change the adapter configuration:

// Before: Google Maps
const [Map, api] = useMap(googleMapsAdapter({ apiKey: "..." }));

// After: Mapbox
const [Map, api] = useMap(mapboxAdapter({ accessToken: "..." }));

// Everything else stays the same!

Configuration Options

Google Maps Config

type GoogleMapsConfig = {
  apiKey: string;              // Required
  theme?: "light" | "dark";    // Default: "light"
  language?: string;           // Map labels language
  initialViewport?: InitialViewport;  // Initial view: center+zoom or bounds
  restrictBounds?: GeoJsonBbox | null;  // Default: no restriction; null = no restriction
};

Mapbox Config

type MapboxConfig = {
  accessToken: string;         // Required
  theme?: "light" | "dark";    // Default: "light"
  language?: string;           // Map labels language
  initialViewport?: InitialViewport;  // Initial view: center+zoom or bounds
  restrictBounds?: GeoJsonBbox | null;  // Default: no restriction; null = no restriction
};

restrictBounds controls panning/zoom bounds. GeoJSON format: [minLng, minLat, maxLng, maxLat].

  • undefined (default): no restriction (infinite horizontal scroll)
  • null: no restriction (infinite horizontal scroll)
  • GeoJsonBbox: restrict to custom region (e.g. Europe: [-10, 35, 40, 70])

Invalid bboxes are validated with geoJsonBboxSchema; on failure, a console warning is emitted and no restriction is applied (fallback to null).

InitialViewport is a discriminated union:

  • { type: "center"; center: [lng, lat]; zoom?: number } — center with optional zoom
  • { type: "bounds"; bounds: GeoJsonBbox; padding?: number; maxZoom?: number } — fit to bounds

Accessibility

Maps include built-in accessibility features:

  • ARIA labels and roles
  • Keyboard navigation:
    • Arrow keys: Pan map (100px)
    • +/-: Zoom in/out (1 level)
    • Home: Reset to default view
    • Tab: Focus map
    • Escape: Deselect/close popups

Architecture

@trackunit/react-map is built in independently-useful layers: provider adapters, the useMap foundation and MapApi, layer hooks (useMarkers, useShapes, …), and atomic components (MapMarker, ClusterMarker, …). Each tier is usable and testable on its own — see the design principles and three-tier model above.

Provider integrations ship as separate packages:

  • @trackunit/react-map-adapter-google — Google Maps
  • @trackunit/react-map-adapter-mapbox — Mapbox

Adding New Providers

To add a new map provider:

  1. Create adapter instance implementing AdapterInstance<TConfig>
  2. Create type converters (GeoJSON ↔ provider types)
  3. Create renderer component
  4. Create adapter factory using defineAdapter()
  5. Export from index.ts

Example structure:

src/adapters/leaflet/
├── leafletAdapter.ts
├── LeafletAdapterInstance.ts
├── LeafletRenderer.tsx
├── leafletTypeConverters.ts
└── constants.ts

For more info and a full guide on Iris App SDK Development, please visit our Developer Hub.

Trackunit

This package was developed by Trackunit ApS. Trackunit is the leading SaaS-based IoT solution for the construction industry, offering an ecosystem of hardware, fleet management software & telematics.

The Trackunit logo