React Simple Maps

<-Docs
Migrating to v5

Guides

Migrating to v5

v5 is a TypeScript rewrite of v3. It fixes a lot of underlying issues that have come up over time and it aims to streamline the API. The component names, their props and the render-prop shape are unchanged, so most maps upgrade with no edits at all. There is one breaking API change — the style prop on Geography and Marker. There are also some behaviour fixes worth knowing about.

This guide focuses on v3 to v5, since v4 was an intermediary fix that never left beta. If you are using v3, you can go straight from v3 to v5. If you are using the v4 beta, this guide should still apply.

npm install react-simple-maps@5

prop-types was a peer dependency in v3 and is gone in v5. If nothing else in your project uses it, you can drop it:

npm uninstall prop-types

At a glance

  • style={{ default, hover, pressed }} has been removed in v5
  • v5 exposes react-simple-maps, react-simple-maps/core and react-simple-maps/zoom imports
  • v5 is fully typed (no need for external @types packages)
  • v5 supports newer react versions
  • prop-types have been removed and replaced with typescript support
  • Bug fixes make some tedious workaround unnecessary

The major breaking change: style

In v3, Geography and Marker took a style object keyed by interaction state, and tracked hover and pressed state internally:

// v3 — no longer works
<Geography
  geography={geo}
  style={{
    default: { fill: "#0066FF", outline: "none" },
    hover: { fill: "#3385FF", outline: "none" },
    pressed: { fill: "#0052CC", outline: "none" },
  }}
/>

While this was useful at the time, this can now easily be achieved with CSS variables. Therefore it's not necessary to override the style prop behaviour. This makes the component easier to reason about.

In v5, style is a plain React style object passed straight to the element. The keyed form no longer does anything — it just fails quietly: React receives an object whose keys are default, hover and pressed, none of which are CSS properties, so the element renders unstyled rather than erroring. There will be no indication that you should change this, so make sure to search your codebase for style={{ near Geography and Marker before you upgrade.

To replace the behaviour in v3 you can just use CSS. Add a class, or use the built-in class names:

// v5
<Geography geography={geo} className="country" />
.country {
  fill: #0066ff;
  outline: none;
}
.country:hover {
  fill: #3385FF;
}
.country:active {
  fill: #0052CC;
}

default maps to the base rule, hover to :hover, and pressed to :active.

Keeping per-geography colors

Where the v3 default fill came from data, move the data-driven part to a CSS variable and leave the states in CSS:

// v3
<Geography geography={geo} style={{
  default: { fill: colorScale(value) },
  hover: { fill: "#3385FF" },
}} />
 
// v5
<Geography geography={geo} style={{ "--fill": colorScale(value) }} />
.rsm-geography {
  fill: var(--fill, #0066ff);
}
.rsm-geography:hover {
  fill: var(--fill-hover, #3385FF);
}

The styling guide covers this pattern in full, including deriving a hover color that fits each generated fill.

If you need the v3 behaviour as-is

For a large codebase, a wrapper gets you migrated without touching every call site. It reproduces v3's state tracking:

import { useState } from "react"
import { Geography } from "react-simple-maps"
 
export function StyledGeography({ style = {}, ...props }) {
  const [hover, setHover] = useState(false)
  const [pressed, setPressed] = useState(false)
 
  return (
    <Geography
      {...props}
      style={pressed ? style.pressed : hover ? style.hover : style.default}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => {
        setHover(false)
        setPressed(false)
      }}
      onMouseDown={() => setPressed(true)}
      onMouseUp={() => setPressed(false)}
    />
  )
}

Treat this as a stepping stone. It re-renders on every hover, which is what CSS avoids — on a map with hundreds of features, moving to CSS can improve performance and simplify state management.

Imports and entry points

Every v3 import still works unchanged:

import {
  ComposableMap,
  Geographies,
  Geography,
  ZoomableGroup,
} from "react-simple-maps"

v5 adds two subpaths. If your map does not pan or zoom, importing from /core leaves d3-zoom and d3-selection out of your bundle:

import { ComposableMap, Geographies, Geography } from "react-simple-maps/core"
import { ZoomableGroup, useZoomPan } from "react-simple-maps/zoom"

This is optional. Mixing them is fine — /core and /zoom are the same modules the root re-exports, so a ZoomableGroup from /zoom works inside a ComposableMap from /core.

TypeScript

v3 shipped no types; projects used @types/react-simple-maps from DefinitelyTyped. v5 bundles its own, so you can remove the community package:

npm uninstall @types/react-simple-maps

Leaving it installed will shadow the bundled types and produce confusing errors.

All prop types are exported if you need them directly:

import type {
  GeographyProps,
  ProjectionConfig,
  Geography,
} from "react-simple-maps"

Two things TypeScript will now flag that v3 accepted silently:

Coordinates must be tuples. [number, number], not number[]:

const coords: [number, number] = [-74.006, 40.7128] // ✓
const coords = [-74.006, 40.7128] as const // ✓
const coords: number[] = [-74.006, 40.7128] // ✗

CSS custom properties need a cast, since React.CSSProperties does not include them:

style={{ "--fill": fill } as React.CSSProperties}

Behaviour fixes

These are bug fixes rather than API changes. They only matter if you worked around them.

Markers and annotations no longer crash off-projection

v3 destructured the projection result directly, so a coordinate the projection could not place threw TypeError: null is not iterable. This bit anyone using geoAlbersUsa, which returns null for anything outside the US.

v5 renders nothing for those coordinates instead. If you filtered your data to dodge the crash, you can stop:

// v3 workaround, no longer needed
{cities.filter((c) => projection(c.coordinates)).map(...)}

Note this is about the projection returning null, not about visibility. On an orthographic globe, d3 projects far-side coordinates onto the visible disc rather than returning null — so markers on the back of the globe still render, in v3 and v5 alike. To hide them, clip to a Sphere or test the coordinate yourself.

Context hooks throw instead of returning undefined

In v3, useMapContext() outside a MapProvider returned undefined, and the failure surfaced later as Cannot read properties of undefined (reading 'projection'). v5 throws immediately:

useMapContext must be used within MapProvider

Same for useZoomPanContext outside a ZoomPanProvider. If you called either defensively and checked for undefined, that check now needs to become "don't call it outside the provider".

Upgrade checklist

  • npm install react-simple-maps@next
  • npm uninstall @types/react-simple-maps if it is installed
  • Search for style={{ on Geography and Marker; convert the keyed form to CSS
  • Optionally switch to /core if you do not use zoom
  • Optionally drop prop-types, useMemo around inline geography, and any off-projection filtering