CVR-autocomplete i React

Lad brugeren skrive et par bogstaver af firmanavnet, vælge virksomheden på en liste og få CVR-nummer, adresse og by udfyldt automatisk. To små komponenter og ingen ekstra afhængigheder.

De to endpoints

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

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

1. Autocomplete-komponenten

Komponenten venter 300 ms efter sidste tastetryk (debounce) og afbryder gamle kald med AbortController, så svarene aldrig kommer i forkert rækkefølge.

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. Formularen der udfyldes

Formularen slår virksomheden op, når brugeren vælger i listen, eller når CVR-feltet indeholder 8 cifre.

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>
  );
}
API'et sender Access-Control-Allow-Origin: *, så du kan kalde det direkte fra browseren i Vite, Next.js, Create React App og lignende, uden proxy og uden nøgle.

Gemmer du data til markedsføring?

Så tjek feltet protected i det fulde opslag. Se reklamebeskyttelse.

Ofte stillede spørgsmål

Virker det med Next.js?

Ja. Komponenterne er almindelige klientkomponenter. I Next.js App Router skal filen starte med "use client".

Hvor mange kald må jeg lave?

Der gælder rimelige rate limits. Med debounce på 300 ms laver autocomplete kun ét kald, når brugeren holder pause.