De to endpoints
https://apicvr.dk/api/v1/search/fuzzy/{navn}finder virksomheder, selv med stavefejl. Perfekt til autocomplete.https://apicvr.dk/api/v1/{cvr}henter den fulde profil, når brugeren har valgt en virksomhed eller tastet et CVR-nummer.
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.