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

geomelon-react

v0.1.0

Published

Headless React hook and component for Geomelon city autocomplete — keyless free tier by default

Downloads

157

Readme

Geomelon React

Headless React hook and component for city autocomplete, built on the Geomelon API's free, keyless oneshot prefix search (oneshot.geomelon.dev) — no API key, no backend proxy required.

Looking for other ways to integrate? See all official libraries at geomelon.dev/libraries.

Installation

npm install geomelon-react

react (>=16.8, for hooks) is a peer dependency — bring your own version. This package depends on geomelon, the zero-dependency Geomelon TypeScript client, for the actual fetch/retry/error logic.

Quick start

The hook

import { useState } from 'react';
import { useCityAutocomplete } from 'geomelon-react';

function CityField() {
  const [query, setQuery] = useState('');
  const { results, loading, error } = useCityAutocomplete({
    countryIso: 'es',
    lang: 'es',
    query,
  });

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Start typing a city..." />
      {loading && <span>Searching…</span>}
      {error && <span>{error.message}</span>}
      <ul>
        {results.map((city) => (
          <li key={city.id}>{city.emoji} {city.name}</li>
        ))}
      </ul>
    </div>
  );
}

useCityAutocomplete debounces (150ms default), cancels the in-flight request whenever countryIso/lang/query change again, and never fires below minLength (default 1). Errors are typed GeomelonError, not silently swallowed into an empty result list.

The component

For a ready-made input + dropdown with keyboard navigation and ARIA combobox semantics, use <CityAutocomplete> — it's headless (no shipped CSS), so style it with className/inputClassName/listClassName/optionClassName:

import { CityAutocomplete } from 'geomelon-react';

<CityAutocomplete
  countryIso="es"
  lang="es"
  placeholder="Start typing a city..."
  onSelect={(city) => console.log(city.name)}
  inputClassName="my-input"
  listClassName="my-dropdown"
  optionClassName={(highlighted) => (highlighted ? 'my-option my-option--active' : 'my-option')}
/>

Keyboard: ArrowUp/ArrowDown move the highlighted option, Enter selects it, Escape closes the list. Mouse click and hover work the same way. renderOption(city, isHighlighted) overrides the default "🇪🇸 Barcelona (Barcelona)"-style row.

Configuration

Both the hook and the component accept:

| Option | Default | Notes | |---|---|---| | countryIso | — | ISO 3166-1 alpha-2 country code, e.g. "es" | | lang | — | BCP 47 language code, e.g. "es" | | debounceMs | 150 | Delay before firing a request after the query stops changing | | minLength | 1 | Minimum trimmed query length before a request fires | | apiKey | — | Optional RapidAPI key — omit to use the free keyless host | | freeOneshot | false | Route to the free keyless host even when apiKey is set |

<CityAutocomplete> additionally takes onSelect, placeholder, renderOption, and the four *ClassName styling props above.

Accessibility

<CityAutocomplete> follows the ARIA combobox pattern: role="combobox" on the input with aria-expanded/aria-controls/aria-activedescendant, role="listbox"/role="option" on the results. If you build your own UI on top of the raw hook, replicate these roles yourself — useCityAutocomplete only manages data, not markup.

Recipes

These are copy-paste starting points, not shipped code — no shadcn/ui or react-hook-form dependency is added by this package.

shadcn/ui combobox

import { useState } from 'react';
import { useCityAutocomplete } from 'geomelon-react';
import { Command, CommandInput, CommandList, CommandItem, CommandEmpty } from '@/components/ui/command';
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';

function CityCombobox({ onSelect }: { onSelect: (name: string) => void }) {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState('');
  const { results, loading } = useCityAutocomplete({ countryIso: 'es', lang: 'es', query });

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <button role="combobox" aria-expanded={open}>{query || 'Select a city...'}</button>
      </PopoverTrigger>
      <PopoverContent className="p-0">
        <Command shouldFilter={false}>
          <CommandInput value={query} onValueChange={setQuery} placeholder="Search cities..." />
          <CommandList>
            {!loading && results.length === 0 && <CommandEmpty>No cities found.</CommandEmpty>}
            {results.map((city) => (
              <CommandItem
                key={city.id}
                onSelect={() => {
                  setQuery(city.name);
                  setOpen(false);
                  onSelect(city.name);
                }}
              >
                {city.emoji} {city.name}
              </CommandItem>
            ))}
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}

react-hook-form

import { Controller, useForm } from 'react-hook-form';
import { CityAutocomplete } from 'geomelon-react';

function Form() {
  const { control, handleSubmit } = useForm({ defaultValues: { city: '' } });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <Controller
        name="city"
        control={control}
        render={({ field }) => (
          <CityAutocomplete
            countryIso="es"
            lang="es"
            onSelect={(city) => field.onChange(city.name)}
          />
        )}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

Example

A minimal runnable app lives in examples/vite-react — clone the repo and npm install && npm run dev, or open it directly in StackBlitz.

Error handling

GeomelonError (re-exported from geomelon) is set on the hook's error field — status, body, url, and an isRateLimited getter (status === 429) are available for any non-OK response or network failure.

Types

OneshotCityDto (re-exported from geomelon): { id, name, population, emoji, en? }en is omitted when the requested language is English or no English translation exists.

Build

npm install
npm run build   # dual CJS + ESM via tsc
npm test        # node:test + @testing-library/react, jsdom via global-jsdom

License

MIT