CVR autocomplete in React

Let users type a few letters of a company name, pick the company from a list, and get the CVR number, address and city filled in automatically. Two small components and no extra dependencies.

The two endpoints

text
GET https://apicvr.dk/api/v1/search/fuzzy/[company name]

→ [{ "name": "...", "cvr_number": 12345678, "industrycode": ..., "industrytext": "..." }, ...]

1. The autocomplete component

The component waits 300 ms after the last keystroke (debounce) and cancels stale requests with AbortController, so responses never arrive out of order.

jsx
import { useEffect, useState } from "react";

const API = "https://apicvr.dk/api/v1";

export function CompanyAutocomplete({ onSelect }) {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);

  useEffect(() => {
    const q = query.trim();
    if (q.length < 2) {
      setResults([]);
      return;
    }
    const controller = new AbortController();
    const timer = setTimeout(async () => {
      try {
        const res = await fetch(`${API}/search/fuzzy/${encodeURIComponent(q)}`, {
          signal: controller.signal,
        });
        if (res.ok) setResults((await res.json()).slice(0, 8));
      } catch (err) {
        if (err.name !== "AbortError") setResults([]);
      }
    }, 300); // debounce: wait until the user pauses typing

    return () => {
      clearTimeout(timer);
      controller.abort();
    };
  }, [query]);

  return (
    <div className="company-autocomplete">
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Company name"
        autoComplete="off"
      />
      {results.length > 0 && (
        <ul role="listbox">
          {results.map((c) => (
            <li key={c.cvr_number} role="option" aria-selected="false">
              <button type="button" onClick={() => { onSelect(c.cvr_number); setResults([]); }}>
                {c.name} <small>CVR {c.cvr_number} · {c.industrytext}</small>
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

2. The form that gets filled in

The form looks up the company when the user picks from the list, or when the CVR field contains 8 digits.

jsx
import { useState } from "react";
import { CompanyAutocomplete } from "./CompanyAutocomplete";

const API = "https://apicvr.dk/api/v1";
const EMPTY = { cvr: "", name: "", address: "", zipcode: "", city: "" };

export function CompanyForm() {
  const [form, setForm] = useState(EMPTY);
  const [error, setError] = useState(null);

  async function fillFromCvr(cvr) {
    setError(null);
    const res = await fetch(`${API}/${cvr}`);
    if (!res.ok) {
      setError(res.status === 404 ? "Unknown CVR number" : "Lookup failed");
      return;
    }
    const c = await res.json();
    setForm({
      cvr: String(c.vat),
      name: c.name ?? "",
      address: c.address ?? "",
      zipcode: c.zipcode ? String(c.zipcode) : "",
      city: c.city ?? "",
    });
  }

  const field = (key) => ({
    value: form[key],
    onChange: (e) => {
      const value = e.target.value;
      setForm((f) => ({ ...f, [key]: value }));
      if (key === "cvr" && /^\d{8}$/.test(value)) fillFromCvr(value);
    },
  });

  return (
    <form>
      <CompanyAutocomplete onSelect={fillFromCvr} />
      <input {...field("cvr")} placeholder="CVR" inputMode="numeric" maxLength={8} />
      <input {...field("name")} placeholder="Name" />
      <input {...field("address")} placeholder="Address" />
      <input {...field("zipcode")} placeholder="Postcode" />
      <input {...field("city")} placeholder="City" />
      {error && <p role="alert">{error}</p>}
    </form>
  );
}
The API sends Access-Control-Allow-Origin: *, so you can call it straight from the browser in Vite, Next.js, Create React App and similar, with no proxy and no key.

Storing data for marketing?

Then check the protected field in the full lookup. See marketing protection.

Frequently asked questions

Does it work with Next.js?

Yes. The components are ordinary client components. In the Next.js App Router, start the file with "use client".

How many requests can I make?

Fair-use rate limits apply. With a 300 ms debounce, the autocomplete only sends one request when the user pauses typing.