Hooks

useDocyrusMapView

Build Docyrus-backed map pages from a locationSelect field with saved views, marker models, and ready-to-render Leaflet or Google Maps canvases.

Client Only

Installation

pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-map-view
Required Packages(6 packages)
pnpm add @docyrus/app-utils @docyrus/api-client @tanstack/react-query react-leaflet leaflet @vis.gl/react-google-maps

This 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-locationSelect field, 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:

  1. locationFieldSlug
  2. first field with type === 'field-locationSelect'

Title field

Resolution order:

  1. titleFieldSlug
  2. first matching slug from: name, title, label, subject, display_name, displayName, commercial_title, short_name
  3. first text-like field that is not the location field

Description fields

Resolution order:

  1. descriptionFieldSlugs
  2. 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-file payloads (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-icon type or the icon slug when omitted
  • Renders the same SVG asset used by DocyrusIcon inside a 32×32 white badge

Priority (markerPriority):

  • 'image' (default): image when present → icon when present → default MapPin
  • 'icon': icon when present → default MapPin (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 via resolveColorHex)
  • 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 when searchThisArea() is called. isViewportStale becomes true when 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.

OptionTypeDefaultDescription
locationFieldSlugstringautoExplicit location field slug. Use when the data source has multiple field-locationSelect fields.
titleFieldSlugstringautoField slug used for marker titles and side-list labels.
descriptionFieldSlugsstring[]autoExtra fields appended into popup text.
markerImageFieldSlugstring—Field whose value resolves to an image URL (e.g. field-image / field-file payload). Used as the marker's circular avatar.
markerIconFieldSlugstringautoField whose value is a Docyrus icon identifier (e.g. huge building-06). Auto-detects field-icon type or the icon slug.
markerIconColorFieldSlugstring—Field whose value is a CSS color (hex, rgb(), Tailwind token like emerald-500). Tints the icon in Leaflet via CSS mask-image.
markerIconColorstring—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.
enableViewportFilterbooleanfalseNarrow 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.
enableMarkerDragbooleanfalseMake 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.
basemapsArray<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.
defaultBasemapstringfirst entryInitially selected basemap id.
markerTemplatestring—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 }AnkaraFallback center used when there are no mapped records.
defaultZoomnumber6Base zoom level when the map starts without fitted bounds.
focusedZoomnumber14Zoom level used when the result set has a single marker.
fitBoundsPaddingnumber48Padding used when fitting multiple markers into view.
autoSelectSingleMarkerbooleantrueOpens the only marker automatically when the query returns exactly one mapped record.
googleMapsApiKeystring | nullVITE_GOOGLE_MAPS_API_KEYAPI 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

PropertyTypeDescription
toolbarReactNodePrebuilt Docyrus data-view toolbar from useDocyrusDataGrid.
tableTable<TData>Underlying TanStack table used by the toolbar and saved-view editor.
itemsTData[]All fetched records for the active view.
markersDocyrusMapMarker<TData>[]Mapped records transformed into provider-agnostic marker models. Filtered by the active viewport when enableViewportFilter is on.
allMarkersDocyrusMapMarker<TData>[]All resolved markers before viewport filtering. Use this for the underlying dataset (e.g. counts or "show all" links).
mappedItemsTData[]Records that produced markers successfully. Also viewport-filtered.
unmappedItemsTData[]Records without usable coordinates.
viewportBoundsDocyrusMapBounds | nullBounds reported by the active map provider on its last moveend / idle event.
setViewportBounds(bounds | null) => voidWired internally — pass to DocyrusLeafletMapView / DocyrusGoogleMapView via onViewportChange.
isViewportStalebooleantrue when the map has moved since the last applied viewport filter (manual mode).
searchThisArea() => voidApply the current viewport as the active filter (manual mode).
clearViewportFilter() => voidRemove the viewport filter and restore the full marker set.
enableMarkerDragbooleanMirrors 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.
availableBasemapsDocyrusBasemapDefinition[]Resolved basemap list after expanding ids. Pass to a map view's basemaps prop.
activeBasemapDocyrusBasemapDefinition | nullCurrently selected basemap. Pass to a map view's activeBasemap prop.
setActiveBasemap(id: string) => voidSwitch the active basemap. Pass to a map view's onBasemapChange prop.
locationFieldDataSourceField | nullResolved field-locationSelect definition.
titleFieldDataSourceField | nullResolved title field definition.
descriptionFieldsDataSourceField[]Resolved popup/description fields.
hasLocationFieldbooleantrue when a usable location field was resolved on the data source.
defaultZoomnumberMirrors the resolved option — pass to a map view's defaultZoom prop.
focusedZoomnumberMirrors the resolved option — pass to a map view's focusedZoom prop.
fitBoundsPaddingnumberMirrors the resolved option — pass to a map view's fitBoundsPadding prop.
viewsSavedDataGridView[]Saved views from useDocyrusDataGrid.
fieldsDataSourceField[]Full field list from useDocyrusDataGrid.
dataSourceDataSourceMetadata | nullResolved Docyrus data source metadata.
activeViewIdstring | nullCurrently active saved view id.
setActiveViewId(id) => voidSwitch the active saved view.
onMapClick((position) => void) | undefinedMirrors the option — pass to a map view's onMapClick prop.
requestedColumnsstringFinal comma-separated columns string sent to the items endpoint.
resolvedListParamsDocyrusDataGridListParamsFull 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 } | nullBounding box for the current marker set.
selectedMarkerIdstring | nullCurrently selected marker id. Updated by popup open/close events.
selectedMarkerDocyrusMapMarker<TData> | nullFull selected marker object.
selectMarker(id: string) => voidProgrammatically select a marker (e.g. from a side list). Pass to onSelectMarker.
setSelectedMarkerId(id: string | null) => voidRaw selection setter — prefer selectMarker / clearSelectedMarker.
clearSelectedMarker() => voidClears the active selection.
counts{ total; mapped; unmapped }Summary metrics for the active result set.
googleMapsApiKeystring | nullResolved Google Maps API key.
reload() => voidRefetches the saved-view metadata and the map items query.
isLoadingbooleanCombined loading state for metadata + items.
errorError | nullFirst query error, if any.

DocyrusLeafletMapView

Renders the hook's marker model on top of the Docyrus UI Leaflet map.

PropTypeDefaultDescription
markersDocyrusMapMarker<TData>[]—Marker models from useDocyrusMapView.
center{ lat; lng }—Center point used when bounds are not available.
boundsDocyrusMapBounds | null—Bounding box used for fitBounds.
defaultZoomnumber6Base zoom level.
focusedZoomnumber14Zoom level for a single marker.
fitBoundsPaddingnumber48Padding used in fitBounds.
selectedMarkerIdstring | 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.
draggableMarkersbooleanfalseMake each marker draggable.
onMarkerMove(marker, newPosition) => void | Promise<void>—Fired after a marker is dropped. Pass mapView.handleMarkerMove for default PATCH+reload behavior.
allMarkersCountnumber—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.
basemapsDocyrusBasemapDefinition[]—Basemap options to surface in the in-map picker. Pass mapView.availableBasemaps. Picker shows when at least 2 entries are provided.
activeBasemapDocyrusBasemapDefinition | null—Currently selected basemap. Pass mapView.activeBasemap.
onBasemapChange(id: string) => void—Called when the user picks a different basemap. Pass mapView.setActiveBasemap.
renderPopup(marker) => ReactNodedefault popupCustom popup renderer.
renderMarkerIcon(marker, isSelected) => ReactNodedefault pinCustom marker icon renderer. Called with isSelected=false on creation and isSelected=true/false imperatively when the popup opens/closes.
emptyStateReactNodebuilt-inCustom empty state when no markers exist.
classNamestring—Wrapper class name.

DocyrusGoogleMapView

Renders the same marker model on Google Maps.

PropTypeDefaultDescription
apiKeystring | nullenvGoogle Maps API key. When absent, the component renders a graceful fallback.
markersDocyrusMapMarker<TData>[]—Marker models from useDocyrusMapView.
center{ lat; lng }—Center point used when bounds are not available.
boundsDocyrusMapBounds | null—Bounding box used for fitBounds.
defaultZoomnumber6Base zoom level.
focusedZoomnumber14Zoom level for a single marker.
fitBoundsPaddingnumber48Padding used in fitBounds.
defaultMapTypeDocyrusGoogleMapType'roadmap'Initial Google mapTypeId. Ignored when activeBasemap is provided.
selectedMarkerIdstring | 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.
draggableMarkersbooleanfalseMake each marker draggable.
onMarkerMove(marker, newPosition) => void | Promise<void>—Fired after a marker is dropped. Pass mapView.handleMarkerMove for default PATCH+reload behavior.
allMarkersCountnumber—Underlying marker count before viewport filtering. Keeps the map mounted when the filtered markers array is empty. Pass mapView.allMarkers.length.
basemapsDocyrusBasemapDefinition[]—Basemap options to surface in the in-map picker. Pass mapView.availableBasemaps. Picker shows when at least 2 entries are provided.
activeBasemapDocyrusBasemapDefinition | 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 | nulldefaultCustom Google icon resolver. Return null to fall through to the built-in image/icon logic.
renderPopup(marker) => ReactNodedefault popupCustom info-window renderer.
emptyStateReactNodebuilt-inCustom empty state when no markers exist.
classNamestring—Wrapper class name.

Type Exports

TypeDescription
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.
DocyrusNormalizedLocationShape used internally for location-field values (lat, lng, plus address/description/details/placeId).
DocyrusLocationValueLoose location-field payload that the hook normalizes (supports both latitude/longitude and lat/lng).
DocyrusMapProvider'leaflet' | 'google'.
DocyrusGoogleMapType'roadmap' | 'satellite' | 'hybrid' | 'terrain'.
DocyrusBasemapIdBuilt-in basemap id: 'street' | 'satellite' | 'hybrid' | 'terrain'.
DocyrusBasemapDefinitionCustom basemap shape: { id, label, leafletUrl, leafletDarkUrl?, leafletAttribution?, googleMapType }.
DocyrusMapMarkerIcon{ url, size? } icon descriptor returned by Google's getMarkerIcon.
DEFAULT_DOCYRUS_BASEMAPSBuilt-in basemap registry, keyed by DocyrusBasemapId. Useful for cloning/extending presets.
UseDocyrusMapViewOptions<TData>Hook option shape (extends UseDocyrusDataGridOptions).
UseDocyrusMapViewResult<TData>Hook return shape.

On this page