npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@creative-locator/react

v0.2.0

Published

React bindings for Creative Locator. Drop-in <DealerLocator /> component + mount-once hooks (useDealerLocator / useDealerLocatorState / useDealerEvents) + Next.js 15 App Router Server Component entry resolved via the react-server exports condition.

Readme

@creative-locator/react

React component bindings for Creative Locator.

A thin wrapper around @creative-locator/headless that gives React applications a one-component install: drop a <DealerLocator /> into your JSX, pass an apiUrl, render. The component derives the host container from a ref so you never have to manage document.querySelector calls or worry about hydration timing.

Install

pnpm add @creative-locator/core @creative-locator/headless @creative-locator/react leaflet leaflet.markercluster

react and react-dom are peer dependencies (^18 || ^19). The package does not bundle React.

Usage

import { DealerLocator } from '@creative-locator/react';
import '@creative-locator/core/styles/style.scss';

export function LocatorPage() {
	return (
		<DealerLocator
			apiUrl="https://example.com/wp-json/creative-locator/v1"
			style={{ height: '600px' }}
		/>
	);
}

The component renders a single <div> as the host element. Sizing is the host application's responsibility — apply height via the style prop, a CSS class, or a parent container. Without a non-zero height, the locator's map will not be visible.

Props

DealerLocator accepts every option from createHeadlessLocator except container (which is derived from the component's internal ref), plus two layout-shaped props for the wrapping <div>:

| Prop | Type | Default | Notes | | ----------------------- | ---------------------------------- | -------- | ----------------------------------------- | | apiUrl | string | — | Required. Creative Locator REST base URL. | | config | Partial<LocatorConfig> | {} | Merged over defaultLocatorConfig. | | apiHeaders | Record<string, string> | — | Auth / nonce headers. | | fetch | typeof globalThis.fetch | global | Override for SSR or test contexts. | | storage | 'browser' \| 'memory' \| Storage | auto | Auto-detects SSR. | | enableGoogleAnalytics | boolean | false | GA bridge off by default in headless. | | licenseTier | 'free' \| 'pro' | 'free' | Gates Pro layouts client-side. | | adapters | Partial<PlatformAdapters> | — | Per-slot adapter overrides (advanced). | | className | string | — | Applied to the wrapping <div>. | | style | React.CSSProperties | — | Applied to the wrapping <div>. |

See @creative-locator/headless for the canonical option reference.

Mount-once contract

Like most map libraries, the locator is expensive to initialize. The component intentionally ignores prop changes after the initial mount — re-rendering with a new apiUrl or config does NOT recreate the locator. If you need to swap configuration at runtime, remount the component via React's key prop:

<DealerLocator key={apiUrl} apiUrl={apiUrl} />

This is the same trade-off Leaflet wrappers (react-leaflet etc.) make for the same reason.

Hooks (#994 PR A)

Three React hooks expose the underlying DealerLocator for templates that want reactive access to locator state and typed event subscription. The component above is the simplest entry; hooks are the idiomatic shape for richer integrations. See docs/adr/ADR-010-framework-adapter-composables.md for the design.

import { useRef } from 'react';
import { useDealerLocator, useDealerLocatorState, useDealerEvents } from '@creative-locator/react';

export function LocatorPage() {
	const root = useRef<HTMLDivElement | null>(null);
	const { locator } = useDealerLocator(root, { apiUrl: '...' });
	const { allDealers, currentRadius } = useDealerLocatorState(locator);
	useDealerEvents(locator, 'marker:click', ({ location }) => {
		console.log('clicked', location.id);
	});

	return (
		<>
			<div ref={root} style={{ height: 600 }} />
			<p>
				{allDealers.length} dealers within {currentRadius} mi.
			</p>
		</>
	);
}

useDealerLocatorState uses useSyncExternalStore so concurrent-mode React reads a consistent snapshot. The snapshot identity is cached per locator — destructured fields keep referential stability across renders when state hasn't moved.

Next.js 15 App Router (#994 PR B)

The package ships a react-server exports condition. Inside a Server Component, import { DealerLocator } from '@creative-locator/react' resolves to the Server Component entry — an async function that renders the static shell HTML server-side, optionally fetches and inlines the initial dealer payload, and hands off interactivity to a Client Component child on hydration.

// app/locator/page.tsx — runs on the server
import { DealerLocator } from '@creative-locator/react';
import '@creative-locator/core/styles/style.scss';

export default function LocatorPage() {
	return (
		<DealerLocator
			apiUrl="https://example.com/wp-json/creative-locator/v1"
			style={{ height: 600 }}
		/>
	);
}

The Server Component issues a fetch(${apiUrl}/dealers?limit=20) itself by default (Next 15's fetch cache + dedupe makes this effectively free) and emits the result as an inline <script type="application/json"> payload that the client-side HydrationOrchestrator.tryHydrateFromInlinePayload() picks up. The user sees dealer markers on first paint, before any REST round-trip resolves on the client.

Pass initialPayload to skip the SC's own fetch when you've already fetched the data upstream:

export default async function LocatorPage() {
	const res = await fetch('https://example.com/wp-json/creative-locator/v1/dealers?limit=50');
	const json = await res.json();
	return (
		<DealerLocator
			apiUrl="https://example.com/wp-json/creative-locator/v1"
			initialPayload={{ dealers: json.dealers, total: json.total }}
			fetchInitialPayload={false}
		/>
	);
}

Server Component props

| Prop | Type | Default | Notes | | --------------------- | -------------------- | -------------------------- | --------------------------------------------------------------------------------------------- | | apiUrl | string | — | Required. Creative Locator REST base URL. | | initialPayload | InlinePayload | — | Pre-fetched { dealers, total, cached_at? }. Skips the SC's own fetch. | | fetchInitialPayload | boolean | true | Set false to skip server-side data resolution (client-only data flow). | | ssrCap | number | 20 | Server-fetch limit. Mirrors WP crloc_ssr_bootstrap_cap. | | bootstrapId | string | ${instanceId}__bootstrap | DOM id of the inline payload <script>. Consumer-passed config.ssrBootstrapId wins if set. | | instanceId | string | crloc-locator-1 | Wrapper element id. Multi-locator pages need a unique value per instance. | | shellOptions | RenderShellOptions | — | Pass-through to renderLocatorShell (mapPanelInert, labels). | | config | Partial<Config> | — | Same as the SPA component. | | apiHeaders | Record<string, …> | — | Serializable headers only — no functions. |

Non-serializable overrides (fetch, adapters) are not exposed here because they can't cross the RSC → Client boundary. Compose your own Client Component if you need them.

Subpath imports (escape hatch)

The react-server condition does the right thing automatically. For cases where you need to bypass the conditional resolution:

  • @creative-locator/react/server — always the Server Component.
  • @creative-locator/react/client — always the legacy SPA component + hooks.

SPA-only consumers

If you're not on the App Router (e.g. still on Pages Router, CRA, or Vite SPA), the package's default condition resolves to the legacy client-only <DealerLocator /> — the same drop-in wrapper that's been here since #515. Nothing changes for those consumers.

Astro

Use the @astrojs/react integration to render the component as a client island:

---
// src/pages/locator.astro
import { DealerLocator } from '@creative-locator/react';
import '@creative-locator/core/styles/style.scss';
---

<DealerLocator
	client:only="react"
	apiUrl={import.meta.env.PUBLIC_LOCATOR_API_URL}
	style={{ height: '600px' }}
/>

client:only="react" skips SSR entirely (Leaflet needs window).

License

GPL-2.0-or-later