useDocyrusMapView
Build Docyrus-backed map pages from a locationSelect field with saved views, marker models, and ready-to-render Leaflet or Google Maps canvases.
Installation
pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-map-viewpnpm add @docyrus/app-utils @docyrus/api-client @tanstack/react-query react-leaflet leaflet @vis.gl/react-google-mapsThis hook is distributed as source. It expects an authenticated Docyrus client, a QueryClientProvider, and a data source that includes at least one field-locationSelect field.
Overview
useDocyrusMapView is the map-first companion to useDocyrusDataGrid:
- loads the active Docyrus data source metadata
- auto-detects the first
field-locationSelectfield, or uses the slug you provide - keeps saved-view filtering, sorting, and search wiring from
useDocyrusDataGrid - appends required map columns (
id, location, title, popup fields) even when a saved view hides them - transforms records into provider-agnostic marker objects
- returns ready-to-render helpers for both Leaflet and Google Maps
The hook is designed for pages where records stay in Docyrus, but the primary surface is a map instead of a table.
Supported map providers
1. Leaflet
Use DocyrusLeafletMapView when you want the built-in Docyrus UI map stack:
- no API key requirement
- theme-aware tile layers
- clustering
- custom Docyrus map controls (zoom, fullscreen, locate)
2. Google Maps
Use DocyrusGoogleMapView when you want Google basemaps.
Provide the key either with the hook/component googleMapsApiKey prop or through VITE_GOOGLE_MAPS_API_KEY.
Usage
Leaflet page
'use client';
import { useDocyrusAuth } from '@docyrus/signin';
import {
DocyrusLeafletMapView,
useDocyrusMapView
} from '@docyrus/ui/library/hooks/use-docyrus-map-view';
import { useBaseOrganizationCollection } from '@/db/collections/base-organization.collection';
export function OrganizationLocationsPage() {
const { client } = useDocyrusAuth();
if (!client) return null;
const collection = useBaseOrganizationCollection();
const mapView = useDocyrusMapView({
client,
appSlug: 'base',
dataSourceSlug: 'organization',
collection,
locationFieldSlug: 'map_location',
titleFieldSlug: 'name',
descriptionFieldSlugs: ['address', 'city'],
searchPlaceholder: 'Search organizations',
enableGroupMenu: false,
enableDisplayMenu: false,
enableRowHeightMenu: false
});
return (
<div className="space-y-4">
{mapView.toolbar}
<DocyrusLeafletMapView
markers={mapView.markers}
center={mapView.center}
bounds={mapView.bounds}
defaultZoom={mapView.defaultZoom}
focusedZoom={mapView.focusedZoom}
selectedMarkerId={mapView.selectedMarkerId}
onSelectMarker={mapView.selectMarker} />
</div>
);
}Google Maps page
'use client';
import { useDocyrusAuth } from '@docyrus/signin';
import {
DocyrusGoogleMapView,
useDocyrusMapView
} from '@docyrus/ui/library/hooks/use-docyrus-map-view';
export function WarehouseMapPage() {
const { client } = useDocyrusAuth();
if (!client) return null;
const mapView = useDocyrusMapView({
client,
appSlug: 'base',
dataSourceSlug: 'warehouse',
locationFieldSlug: 'location',
titleFieldSlug: 'name',
descriptionFieldSlugs: ['city', 'status'],
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY
});
return (
<DocyrusGoogleMapView
apiKey={mapView.googleMapsApiKey}
markers={mapView.markers}
center={mapView.center}
bounds={mapView.bounds}
selectedMarkerId={mapView.selectedMarkerId}
onSelectMarker={mapView.selectMarker} />
);
}How the query is built
Internally the hook reuses the saved-view state from useDocyrusDataGrid, then creates its own items query with a stronger columns list.
That means a user can save a view that hides map_location, but the hook will still request:
id- the location field
- the resolved title field
- any popup/description fields you configured
So markers keep rendering correctly while the rest of the data-view behavior still follows the active Docyrus view.
Field resolution
Location field
Resolution order:
locationFieldSlug- first field with
type === 'field-locationSelect'
Title field
Resolution order:
titleFieldSlug- first matching slug from:
name,title,label,subject,display_name,displayName,commercial_title,short_name - first text-like field that is not the location field
Description fields
Resolution order:
descriptionFieldSlugs- first matching slugs from:
description,details,address,city,district,country,state,province,status,type
Marker visual (image + icon)
Each marker can render as either a circular avatar (from an image field) or a Docyrus icon, with a configurable priority.
Image field (markerImageFieldSlug):
- Reads
field-image/field-filepayloads (array of{ signed_url, url, src }) - Renders as a 36×36 circular avatar with a white ring
Icon field (markerIconFieldSlug):
- Reads a Docyrus icon identifier string such as
"huge building-06"or"fal star" - Auto-detected from
field-icontype or theiconslug when omitted - Renders the same SVG asset used by
DocyrusIconinside a 32×32 white badge
Priority (markerPriority):
'image'(default): image when present → icon when present → defaultMapPin'icon': icon when present → defaultMapPin(image is ignored)
Icon color (markerIconColorFieldSlug + markerIconColor):
- Per-record color via the field, with a static fallback through
markerIconColor - Accepts hex,
rgb(), CSS variables, or Tailwind tokens like"emerald-500"(resolved viaresolveColorHex) - Leaflet recolors the icon itself via CSS
mask-image - Google Maps renders the icon URL natively and does not support recoloring; the color value is ignored on that provider
The hook exposes the resolved values on each marker as marker.imageUrl, marker.iconUrl, and marker.iconColor. Custom renderMarkerIcon / getMarkerIcon callbacks should return null (Leaflet) or fall through (Google) when either is set to let the built-in renderer take over.
Viewport filter
When enableViewportFilter is true, markers / mappedItems are narrowed to records whose position falls inside the active map bounds. allMarkers keeps the unfiltered set.
Two modes:
auto(default) — every pan/zoom updates the filter. Side lists and counts feel "live".manual— the filter only applies whensearchThisArea()is called.isViewportStalebecomestruewhen the user moves the map without confirming, perfect for an Airbnb-style button.
// Manual mode "Search this area" button
{mapView.isViewportStale && (
<Button onClick={mapView.searchThisArea}>Search this area</Button>
)}Wire the map → hook connection by passing mapView.setViewportBounds to the onViewportChange prop of DocyrusLeafletMapView / DocyrusGoogleMapView.
API Reference
useDocyrusMapView(options)
The hook accepts all useDocyrusDataGrid options except enableItemsQuery, showSelectColumn, and enableRowMarkers, plus the options below.
DB-free metadata. Inherited through useDocyrusDataGrid → useDocyrusDataViewSelect: dataSource (inject a pre-resolved schema → skips the getBySlug fetch), enableDataViews: false (skip the /views fetch), and dataSourceExpand (tune/drop the schema expand param). Pass dataSource together with data (pre-resolved rows) to render markers with no metadata requests. See DB-free metadata.
| Option | Type | Default | Description |
|---|---|---|---|
locationFieldSlug | string | auto | Explicit location field slug. Use when the data source has multiple field-locationSelect fields. |
titleFieldSlug | string | auto | Field slug used for marker titles and side-list labels. |
descriptionFieldSlugs | string[] | auto | Extra fields appended into popup text. |
markerImageFieldSlug | string | — | Field whose value resolves to an image URL (e.g. field-image / field-file payload). Used as the marker's circular avatar. |
markerIconFieldSlug | string | auto | Field whose value is a Docyrus icon identifier (e.g. huge building-06). Auto-detects field-icon type or the icon slug. |
markerIconColorFieldSlug | string | — | Field whose value is a CSS color (hex, rgb(), Tailwind token like emerald-500). Tints the icon in Leaflet via CSS mask-image. |
markerIconColor | string | — | Static fallback color used when the per-record color field is empty. |
markerPriority | 'image' | 'icon' | 'image' | Which source wins when both fields are populated. 'image' falls back to icon when image is empty; 'icon' always renders the icon when present. |
enableViewportFilter | boolean | false | Narrow markers / mappedItems to records inside the map's current visible bounds. allMarkers keeps the unfiltered set. |
viewportFilterMode | 'auto' | 'manual' | 'auto' | 'auto' re-filters on every pan/zoom. 'manual' only filters when searchThisArea() is called — pair with an Airbnb-style "Search this area" button via isViewportStale. |
enableMarkerDrag | boolean | false | Make every marker draggable. On drop, the hook calls onMarkerMove (if provided) or PATCHes the resolved location field of the record. |
onMarkerMove | (marker, newPosition) => void | Promise<void> | — | Custom drop handler. Default behavior: reverse-geocodes the new coordinates, PATCHes the location field with { latitude, longitude, address }, then reload()s. |
basemaps | Array<DocyrusBasemapId | DocyrusBasemapDefinition> | false | — | Basemaps to surface in the in-map picker. Built-in ids: 'street', 'satellite', 'hybrid', 'terrain'. Pass custom DocyrusBasemapDefinition for non-default tile servers. |
defaultBasemap | string | first entry | Initially selected basemap id. |
markerTemplate | string | — | Handlebars-style template for popup body text (e.g. "{{name}} — {{city}}"). |
getMarker | (item, context) => DocyrusMapMarker | null | — | Override marker generation completely. Return null to skip a record. |
defaultCenter | { lat: number; lng: number } | Ankara | Fallback center used when there are no mapped records. |
defaultZoom | number | 6 | Base zoom level when the map starts without fitted bounds. |
focusedZoom | number | 14 | Zoom level used when the result set has a single marker. |
fitBoundsPadding | number | 48 | Padding used when fitting multiple markers into view. |
autoSelectSingleMarker | boolean | true | Opens the only marker automatically when the query returns exactly one mapped record. |
googleMapsApiKey | string | null | VITE_GOOGLE_MAPS_API_KEY | API key forwarded to DocyrusGoogleMapView. |
onMapClick | (position: DocyrusMapPoint) => void | — | Called when the user clicks an empty area of the map (not a marker). Used to power click-to-locate flows for unmapped records. |
Return value
| Property | Type | Description |
|---|---|---|
toolbar | ReactNode | Prebuilt Docyrus data-view toolbar from useDocyrusDataGrid. |
table | Table<TData> | Underlying TanStack table used by the toolbar and saved-view editor. |
items | TData[] | All fetched records for the active view. |
markers | DocyrusMapMarker<TData>[] | Mapped records transformed into provider-agnostic marker models. Filtered by the active viewport when enableViewportFilter is on. |
allMarkers | DocyrusMapMarker<TData>[] | All resolved markers before viewport filtering. Use this for the underlying dataset (e.g. counts or "show all" links). |
mappedItems | TData[] | Records that produced markers successfully. Also viewport-filtered. |
unmappedItems | TData[] | Records without usable coordinates. |
viewportBounds | DocyrusMapBounds | null | Bounds reported by the active map provider on its last moveend / idle event. |
setViewportBounds | (bounds | null) => void | Wired internally — pass to DocyrusLeafletMapView / DocyrusGoogleMapView via onViewportChange. |
isViewportStale | boolean | true when the map has moved since the last applied viewport filter (manual mode). |
searchThisArea | () => void | Apply the current viewport as the active filter (manual mode). |
clearViewportFilter | () => void | Remove the viewport filter and restore the full marker set. |
enableMarkerDrag | boolean | Mirrors the input option — pass to a map view's draggableMarkers prop. |
handleMarkerMove | (marker, newPosition) => Promise<void> | Default drop handler wired to the Docyrus client — pass to a map view's onMarkerMove prop. |
availableBasemaps | DocyrusBasemapDefinition[] | Resolved basemap list after expanding ids. Pass to a map view's basemaps prop. |
activeBasemap | DocyrusBasemapDefinition | null | Currently selected basemap. Pass to a map view's activeBasemap prop. |
setActiveBasemap | (id: string) => void | Switch the active basemap. Pass to a map view's onBasemapChange prop. |
locationField | DataSourceField | null | Resolved field-locationSelect definition. |
titleField | DataSourceField | null | Resolved title field definition. |
descriptionFields | DataSourceField[] | Resolved popup/description fields. |
hasLocationField | boolean | true when a usable location field was resolved on the data source. |
defaultZoom | number | Mirrors the resolved option — pass to a map view's defaultZoom prop. |
focusedZoom | number | Mirrors the resolved option — pass to a map view's focusedZoom prop. |
fitBoundsPadding | number | Mirrors the resolved option — pass to a map view's fitBoundsPadding prop. |
views | SavedDataGridView[] | Saved views from useDocyrusDataGrid. |
fields | DataSourceField[] | Full field list from useDocyrusDataGrid. |
dataSource | DataSourceMetadata | null | Resolved Docyrus data source metadata. |
activeViewId | string | null | Currently active saved view id. |
setActiveViewId | (id) => void | Switch the active saved view. |
onMapClick | ((position) => void) | undefined | Mirrors the option — pass to a map view's onMapClick prop. |
requestedColumns | string | Final comma-separated columns string sent to the items endpoint. |
resolvedListParams | DocyrusDataGridListParams | Full query payload passed to the Docyrus items endpoint. |
center | { lat: number; lng: number } | Computed center for the current marker set. |
bounds | { north; south; east; west } | null | Bounding box for the current marker set. |
selectedMarkerId | string | null | Currently selected marker id. Updated by popup open/close events. |
selectedMarker | DocyrusMapMarker<TData> | null | Full selected marker object. |
selectMarker | (id: string) => void | Programmatically select a marker (e.g. from a side list). Pass to onSelectMarker. |
setSelectedMarkerId | (id: string | null) => void | Raw selection setter — prefer selectMarker / clearSelectedMarker. |
clearSelectedMarker | () => void | Clears the active selection. |
counts | { total; mapped; unmapped } | Summary metrics for the active result set. |
googleMapsApiKey | string | null | Resolved Google Maps API key. |
reload | () => void | Refetches the saved-view metadata and the map items query. |
isLoading | boolean | Combined loading state for metadata + items. |
error | Error | null | First query error, if any. |
DocyrusLeafletMapView
Renders the hook's marker model on top of the Docyrus UI Leaflet map.
| Prop | Type | Default | Description |
|---|---|---|---|
markers | DocyrusMapMarker<TData>[] | — | Marker models from useDocyrusMapView. |
center | { lat; lng } | — | Center point used when bounds are not available. |
bounds | DocyrusMapBounds | null | — | Bounding box used for fitBounds. |
defaultZoom | number | 6 | Base zoom level. |
focusedZoom | number | 14 | Zoom level for a single marker. |
fitBoundsPadding | number | 48 | Padding used in fitBounds. |
selectedMarkerId | string | null | — | Tracks the currently selected marker for list ↔ map synchronization. Does not control popup visibility directly — popups are managed by Leaflet natively. |
onSelectMarker | (id: string | null) => void | — | Called when a marker popup opens or closes, and when the map background is clicked. |
onMapClick | (position) => void | — | Called on a click on empty map area (not a marker). Reverse-geocodes the position and re-fires with address. |
onEditMarker | (marker) => void | — | Edit button handler used by the built-in popup. |
onViewportChange | (bounds | null) => void | — | Fires on moveend with the new visible bounds. Pass mapView.setViewportBounds here to power viewport filtering. |
draggableMarkers | boolean | false | Make each marker draggable. |
onMarkerMove | (marker, newPosition) => void | Promise<void> | — | Fired after a marker is dropped. Pass mapView.handleMarkerMove for default PATCH+reload behavior. |
allMarkersCount | number | — | Underlying marker count before viewport filtering. When provided and non-zero, the map keeps rendering even if the filtered markers array is empty (so panning to an area without records doesn't unmount the map). Pass mapView.allMarkers.length. |
basemaps | DocyrusBasemapDefinition[] | — | Basemap options to surface in the in-map picker. Pass mapView.availableBasemaps. Picker shows when at least 2 entries are provided. |
activeBasemap | DocyrusBasemapDefinition | null | — | Currently selected basemap. Pass mapView.activeBasemap. |
onBasemapChange | (id: string) => void | — | Called when the user picks a different basemap. Pass mapView.setActiveBasemap. |
renderPopup | (marker) => ReactNode | default popup | Custom popup renderer. |
renderMarkerIcon | (marker, isSelected) => ReactNode | default pin | Custom marker icon renderer. Called with isSelected=false on creation and isSelected=true/false imperatively when the popup opens/closes. |
emptyState | ReactNode | built-in | Custom empty state when no markers exist. |
className | string | — | Wrapper class name. |
DocyrusGoogleMapView
Renders the same marker model on Google Maps.
| Prop | Type | Default | Description |
|---|---|---|---|
apiKey | string | null | env | Google Maps API key. When absent, the component renders a graceful fallback. |
markers | DocyrusMapMarker<TData>[] | — | Marker models from useDocyrusMapView. |
center | { lat; lng } | — | Center point used when bounds are not available. |
bounds | DocyrusMapBounds | null | — | Bounding box used for fitBounds. |
defaultZoom | number | 6 | Base zoom level. |
focusedZoom | number | 14 | Zoom level for a single marker. |
fitBoundsPadding | number | 48 | Padding used in fitBounds. |
defaultMapType | DocyrusGoogleMapType | 'roadmap' | Initial Google mapTypeId. Ignored when activeBasemap is provided. |
selectedMarkerId | string | null | — | Opens the selected marker popup. |
onSelectMarker | (id: string | null) => void | — | Called when a marker or the map background is clicked. |
onMapClick | (position) => void | — | Called on a click on empty map area (not a marker). |
onEditMarker | (marker) => void | — | Edit button handler used by the built-in popup. |
onViewportChange | (bounds | null) => void | — | Fires on Google's idle event with the new visible bounds. Pass mapView.setViewportBounds here to power viewport filtering. |
draggableMarkers | boolean | false | Make each marker draggable. |
onMarkerMove | (marker, newPosition) => void | Promise<void> | — | Fired after a marker is dropped. Pass mapView.handleMarkerMove for default PATCH+reload behavior. |
allMarkersCount | number | — | Underlying marker count before viewport filtering. Keeps the map mounted when the filtered markers array is empty. Pass mapView.allMarkers.length. |
basemaps | DocyrusBasemapDefinition[] | — | Basemap options to surface in the in-map picker. Pass mapView.availableBasemaps. Picker shows when at least 2 entries are provided. |
activeBasemap | DocyrusBasemapDefinition | null | — | Currently selected basemap. Pass mapView.activeBasemap. |
onBasemapChange | (id: string) => void | — | Called when the user picks a different basemap. Pass mapView.setActiveBasemap. |
getMarkerIcon | (marker, isSelected) => string | DocyrusMapMarkerIcon | null | default | Custom Google icon resolver. Return null to fall through to the built-in image/icon logic. |
renderPopup | (marker) => ReactNode | default popup | Custom info-window renderer. |
emptyState | ReactNode | built-in | Custom empty state when no markers exist. |
className | string | — | Wrapper class name. |
Type Exports
| Type | Description |
|---|---|
DocyrusMapMarker<TData> | Provider-agnostic marker model with id, title, position, optional imageUrl / iconUrl / iconColor, and the underlying item. |
DocyrusMapMarkerBuilderContext<TData> | Context passed to a custom getMarker builder. |
DocyrusMapPoint | { lat: number; lng: number }. |
DocyrusMapBounds | { north; south; east; west } rectangle. |
DocyrusNormalizedLocation | Shape used internally for location-field values (lat, lng, plus address/description/details/placeId). |
DocyrusLocationValue | Loose location-field payload that the hook normalizes (supports both latitude/longitude and lat/lng). |
DocyrusMapProvider | 'leaflet' | 'google'. |
DocyrusGoogleMapType | 'roadmap' | 'satellite' | 'hybrid' | 'terrain'. |
DocyrusBasemapId | Built-in basemap id: 'street' | 'satellite' | 'hybrid' | 'terrain'. |
DocyrusBasemapDefinition | Custom basemap shape: { id, label, leafletUrl, leafletDarkUrl?, leafletAttribution?, googleMapType }. |
DocyrusMapMarkerIcon | { url, size? } icon descriptor returned by Google's getMarkerIcon. |
DEFAULT_DOCYRUS_BASEMAPS | Built-in basemap registry, keyed by DocyrusBasemapId. Useful for cloning/extending presets. |
UseDocyrusMapViewOptions<TData> | Hook option shape (extends UseDocyrusDataGridOptions). |
UseDocyrusMapViewResult<TData> | Hook return shape. |