# How GeoLibre's Store-Driven Architecture Synchronizes with MapLibre Using Zustand

> Discover how GeoLibre's store-driven architecture synchronizes with MapLibre using Zustand. Learn how state changes in Zustand update MapLibre GL for seamless synchronization.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: architecture
- Published: 2026-08-04

---

**GeoLibre maintains a single Zustand store as the application's source of truth, where the MapController subscribes to state changes and executes a reconciliation algorithm via `syncLayers()` to keep the MapLibre GL viewport perfectly in sync with the store.**

GeoLibre implements a store-driven architecture that centralizes all application state in a Zustand store defined in `@geolibre/core`. This design pattern ensures that UI components never manipulate the map directly; instead, they update the store, which triggers a deterministic synchronization process that updates MapLibre GL layers, sources, and styling.

## The Single Source of Truth Pattern

### Centralized State Management

All application state—including layers, project settings, and selection—lives in [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts). The store is exported as `useStore` and provides slices like `layers` that components subscribe to using Zustand hooks.

### Immutable State Updates

When users interact with the UI—such as adding a GeoJSON file or toggling layer visibility—components call `useStore.setState()` or helper functions like `setLayers`. These updates trigger subscriptions across the application, including the critical subscription held by the map controller.

## The Synchronization Loop

### Store Subscription in MapCanvas

The `MapCanvas` component ([`packages/map/src/MapCanvas.tsx`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/MapCanvas.tsx)) subscribes to the `layers` slice of the Zustand store. When the `layers` array changes, it invokes `controller.syncLayers(layers)` to initiate reconciliation.

### The Reconciliation Algorithm

The `MapController.syncLayers()` method ([`packages/map/src/map-controller.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts)) executes a three-step reconciliation process:

1. **Creates** new MapLibre sources and style layers for any added `GeoLibreLayer` objects.
2. **Updates** existing MapLibre layers when properties like visibility, opacity, or styling change.
3. **Removes** MapLibre resources that correspond to layers deleted from the store.

This logic delegates to helper functions in [`packages/map/src/layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/layer-sync.ts) that translate GeoLibre layer definitions into MapLibre-compatible sources and style layers.

## Practical Implementation Examples

### Adding a GeoJSON Layer

When users import data through a file dialog, the application calls core helpers that update the store:

```tsx
import { useStore, addGeoJsonLayer } from '@geolibre/core';

const handleFile = async (geojson: GeoJSON.FeatureCollection) => {
  // Updates Zustand store; triggers MapController.syncLayers automatically
  addGeoJsonLayer(geojson, {
    name: 'My uploaded data',
    visible: true,
  });
};

```

### Toggling Layer Visibility

Components read from and write to the store using Zustand selectors:

```tsx
import { useStore } from '@geolibre/core';

function VisibilityToggle({ layerId }: { layerId: string }) {
  const visible = useStore(state => 
    state.layers.find(l => l.id === layerId)?.visible ?? false
  );

  const toggle = () => {
    useStore.setState(state => ({
      layers: state.layers.map(l =>
        l.id === layerId ? { ...l, visible: !l.visible } : l
      ),
    }));
  };

  return <button onClick={toggle}>{visible ? 'Hide' : 'Show'}</button>;
}

```

When the button updates the store, `syncLayers` adjusts the corresponding MapLibre layer's `layout.visibility` property without requiring direct map access from the UI component.

### Supporting Alternative Renderers

The store-driven architecture enables alternative renderers to subscribe to the same state. The `CesiumLayerSync` class ([`packages/map/src/cesium-layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-layer-sync.ts)) demonstrates this by implementing its own synchronization logic:

```ts
import { useStore } from '@geolibre/core';
import { CesiumLayerSync } from '@geolibre/map/cesium-layer-sync';

const cesiumSync = new CesiumLayerSync();

useStore.subscribe(state => state.layers, (layers) => {
  cesiumSync.syncLayers(layers); // Applies same state to 3D globe
});

```

## Key Implementation Files

- **[`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts)**: Defines the central Zustand store with slices for `layers`, `project`, and `selection`.
- **[`packages/map/src/map-controller.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts)**: Contains the `MapController` class with the `syncLayers()` reconciliation method.
- **[`packages/map/src/layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/layer-sync.ts)**: Low-level helpers that translate `GeoLibreLayer` objects into MapLibre sources and style layers.
- **[`packages/map/src/MapCanvas.tsx`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/MapCanvas.tsx)**: React component that subscribes to store changes and instantiates the MapLibre map.
- **[`packages/map/src/cesium-layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-layer-sync.ts)**: Reference implementation for alternative renderers subscribing to the same store.

## Summary

- GeoLibre uses a **single Zustand store** (`useStore`) as the exclusive source of truth for all application state.
- The **MapController** subscribes to store changes and executes `syncLayers()` to reconcile state with MapLibre GL.
- UI components **never mutate MapLibre directly**; they update the store via `useStore.setState()` or helper functions.
- The reconciliation algorithm handles **creation, updates, and removal** of MapLibre sources and layers deterministically.
- Alternative renderers like Cesium can subscribe to the same store, making the architecture **extensible and renderer-agnostic**.

## Frequently Asked Questions

### Why does GeoLibre use Zustand instead of React Context for state management?

Zustand provides better performance characteristics for high-frequency updates and eliminates the provider nesting required by React Context. The store-driven architecture in GeoLibre benefits from Zustand's subscription model, which allows the `MapController` to listen specifically to the `layers` slice without re-rendering unrelated UI components when map data changes.

### How does GeoLibre handle performance when syncing hundreds of layers?

The `syncLayers()` method in [`packages/map/src/map-controller.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/map-controller.ts) implements a diffing algorithm that only creates, updates, or removes MapLibre resources that have actually changed. By comparing the previous and current `layers` arrays from the Zustand store, the controller avoids redundant API calls to MapLibre GL, ensuring efficient synchronization even with complex layer hierarchies.

### Can I use GeoLibre's store without the MapLibre renderer?

Yes. The `@geolibre/core` package is designed to be renderer-agnostic. You can import `useStore` and the layer management helpers without importing the map package. The `CesiumLayerSync` implementation in [`packages/map/src/cesium-layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/cesium-layer-sync.ts) demonstrates how to create custom synchronization logic for alternative rendering engines while using the same Zustand store.

### What happens if MapLibre GL throws an error during synchronization?

The `syncLayers()` method includes error boundaries that catch MapLibre GL exceptions during layer creation or updates. When an error occurs, the controller rolls back the specific MapLibre changes while preserving the Zustand store state, allowing the application to display user-friendly error messages without crashing the map instance.