Comparison
See the difference between imperative and declarative map development
Building interactive maps with raw Mapbox GL JS requires a lot of imperative code, DOM manipulation, and careful state management. Terrae simplifies this with a declarative, component-based approach that feels natural in React.
Example
Let's create a map with a marker, popup, route line, and controls. Both approaches produce the exact same result:
Without Terrae
Using Mapbox GL JS directly requires managing the map lifecycle, creating DOM elements manually, and writing imperative code for each feature.
import mapboxgl from 'mapbox-gl';
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN;
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-74.006, 40.7128],
zoom: 12,
});
map.on('load', () => {
// Add marker
const el = document.createElement('div');
el.id = 'marker';
el.style.backgroundImage = 'url(marker.png)';
el.style.width = '32px';
el.style.height = '32px';
el.style.backgroundSize = '100%';
el.style.cursor = 'pointer';
const marker = new mapboxgl.Marker(el)
.setLngLat([-74.006, 40.7128])
.addTo(map);
// Add popup
const popup = new mapboxgl.Popup({ offset: 25 })
.setLngLat([-74.006, 40.7128])
.setHTML('<h3>New York City</h3><p>The city that never sleeps</p>');
el.addEventListener('click', () => {
popup.addTo(map);
});
// Add line
map.addSource('route', {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [
[-74.006, 40.7128],
[-73.9857, 40.7484],
[-73.9772, 40.7527],
],
},
},
});
map.addLayer({
id: 'route',
type: 'line',
source: 'route',
layout: {
'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': '#3b82f6',
'line-width': 4,
},
});
// Add controls
map.addControl(new mapboxgl.NavigationControl());
map.addControl(
new mapboxgl.FullscreenControl()
);
});With Terrae
Terrae provides a React-friendly, declarative API. Simply compose components together and the library handles all the complexity under the hood.
import { Map, MapMarker, MarkerContent, MarkerPopup, MapLine, MapControls, MapZoom, MapFullscreen } from "@/registry/map";
export function MyMap() {
const route = [
[-74.006, 40.7128],
[-73.9857, 40.7484],
[-73.9772, 40.7527],
];
return (
<Map
accessToken={process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN!}
center={[-74.006, 40.7128]}
zoom={12}
>
<MapMarker coordinates={[-74.006, 40.7128]}>
<MarkerContent>
<div className="size-8 rounded-full bg-blue-500 shadow-lg" />
</MarkerContent>
<MarkerPopup>
<h3>New York City</h3>
<p>The city that never sleeps</p>
</MarkerPopup>
</MapMarker>
<MapLine
coordinates={route}
color="#3b82f6"
width={4}
/>
<MapControls position="bottom-right">
<MapZoom />
<MapFullscreen />
</MapControls>
</Map>
);
}Key Differences
| Without Terrae | With Terrae |
|---|---|
| Manual DOM creation and manipulation | Declarative React components |
| Imperative API with many setup steps | Familiar React patterns and hooks |
| Complex lifecycle management | Automatic lifecycle management |
| Boilerplate code for common tasks | Minimal code for common tasks |
| Difficult to compose features together | Easy to compose components |
| No built-in theme support | Automatic light/dark theme support |
| Limited type safety | Type-safe with full TypeScript support |