Skip to content

Reactive Patterns in marimo

This is the most important pattern in the library. marimo re-runs a cell whenever anything it depends on changes. If the cell that creates your Map depends on a slider, every slider tick constructs a brand-new widget: basemap tiles reload, the screen flashes black, and the camera snaps back to its constructor defaults.

The stable-map pattern

Create the map once, in a cell with no control dependencies; update its layers from a separate cell:

# Cell 1 — controls
radius = mo.ui.slider(200, 5000, value=1000, label="Radius")

# Cell 2 — create the map (NO slider deps — never re-executes)
deck_map = dgl.Map(basemap="dark-matter", center=(-1.4, 52.2), zoom=6, pitch=40)
widget = deck_map.as_widget()

# Cell 3 — display
widget

# Cell 4 — update layers reactively (re-executes on slider change)
deck_map.set_layers([
    dgl.HexagonLayer(
        data=df,
        get_position=["lon", "lat"],
        radius=radius.value,
        extruded=True,
    )
])

# Cell 5 — read state back
vp = widget.value.get("viewport", {})
mo.md(f"Zoom: {vp.get('zoom', 0):.1f}")

Slider changes now flow to the existing widget through traitlet sync — the map instance, its tiles, and your camera position all survive.

Why set_layers and not layer_specs?

Map.set_layers(layers) re-serializes the layer specs and re-packs binary buffers in one call. Assigning map.layer_specs = [...] directly is the low-level escape hatch — it works for JSON layers but silently skips binary re-packing, so binary layers (use_binary=True) break under it.

Other update methods

deck_map.add_layer(layer)              # append one layer
deck_map.remove_layer("layer-id")      # remove by id
deck_map.update_layer("layer-id", get_radius=500, opacity=0.5)

update_layer validates prop names and suggests corrections for typos.

The anti-pattern, for reference

# DON'T: rebuilding the widget every slider change
dgl.Map(layers=[dgl.HexagonLayer(radius=radius.value, ...)]).as_widget()

This is an architectural consequence of marimo's fresh-instance-per-run reactivity, not a bug (issue #5 tracks ideas for preserving the camera across re-creation).