Stop med bare at stire på fejl i console, begynd at debugge rigtigt

“Uncaught TypeError” står i console, men din kode ser jo “rigtig” ud?

Første gang jeg fik Uncaught TypeError: Cannot read properties of undefined, læste jeg fejlen, kiggede på min kode i 3 minutter og tænkte: “Men… det burde jo virke”. Så prøvede jeg at gemme, reloade, kopiere, indsætte, bande lidt og til sidst bare kommentere linjen ud.

Det hjalp ikke på det grundlæggende problem: Jeg havde ingen fast måde at fejlfinde på. Jeg reagerede bare på panik.

Det er det, jeg gerne vil undgå for dig. I stedet får du her en ret jordnær måde at tænke JavaScript fejlfindning på, med de typiske fejltyper og konkrete skridt i DevTools. Ikke magi, bare håndværk.

Før du ændrer noget: en lille 5-step debugging-rutine

Jeg starter efterhånden næsten alle fejl med den samme lille liste i hovedet. Når jeg hopper over den, bruger jeg længere tid.

1. Reproducer fejlen bevidst

Fejl der kommer “nogle gange” er næsten umulige at arbejde med. Prøv at gøre det så konkret som muligt:

  • Hvilket klik/handling giver fejlen?
  • På hvilken side/URL?
  • Kommer den hver gang, eller kun efter en bestemt rækkefølge?

Hold DevTools åbent mens du gør det. I Chrome/Edge/Brave: højreklik → Inspect → fanen Console.

2. Læs hele fejlbeskeden, ikke kun første linje

Mange stopper ved første linje:

Uncaught TypeError: Cannot read properties of undefined (reading 'value')

Men nedenunder står der typisk en stack trace:

at handleSubmit (app.js:42)
at HTMLButtonElement.<anonymous> (app.js:10)

Klik på linjen (fx app.js:42), så hopper DevTools til den konkrete linje under fanen Sources.

3. Isolér linjen der fejler

Find den eneste linje, du vil fokusere på først. Hvis du har noget i stil med:

const value = form.querySelector('input').value.trim();

så prøv at splitte det op:

const input = form.querySelector('input');
console.log('input:', input);
const value = input.value.trim();

Nu kan du i console se, om det er input, der er null/undefined, eller om det er value, den snubler over.

4. Log værdier målrettet

Brug console.log som en slags røntgenbillede. Men log kun det, du er i tvivl om:

console.log('user before save:', user);
console.log('user.id type:', typeof user.id);

Hvis du spammer 20 logs, blander de sig sammen, og du får mindre overblik, ikke mere. En god tommelfingerregel: hvis du ikke kan forklare hvorfor du logger noget, så lad være.

5. Verificér at dit fix faktisk virker

Når du tror fejlen er fikset, så:

  • Reload siden (Ctrl+R / Cmd+R).
  • Gør præcis det samme, som gav fejlen første gang.
  • Tjek console igen.

Hvis du ikke kan genskabe fejlen på den måde, er du videre. Hvis der kommer en ny fejl, så tag den som en ny sag, ikke som en del af den gamle.

TypeError: undefined/null – når ting ikke er det, du tror

De mest klassiske fejl jeg ser (og selv laver) er TypeErrors, hvor man prøver at bruge en værdi, der ikke findes. Typisk noget med undefined eller null.

1. Cannot read properties of undefined/null

Eksempel:

const user = getUser();
console.log(user.name.toLowerCase());

Fejl:

Uncaught TypeError: Cannot read properties of undefined (reading 'name')

Oversat: user er undefined, så der er ikke noget at læse .name fra.

Det, der hjalp mig her, var konsekvent at indføre små checks:

const user = getUser();
console.log('user:', user);

if (!user) {
  console.warn('Ingen bruger fundet');
} else {
  console.log(user.name.toLowerCase());
}

Ja, det er mere kode. Til gengæld får du det tydeligt at vide, når getUser() ikke returnerer det, du havde forestillet dig.

2. Sikker adgang med optional chaining

Hvis du har dybe objekter, er optional chaining ret rar:

console.log(user?.profile?.address?.city);

Hvis profile eller address mangler, får du bare undefined i stedet for en TypeError. Det fjerner ikke logikken, men det kan gøre UI-kode mindre skrøbelig.

ReferenceError: variabler og scope

ReferenceError betyder typisk: “Du bruger et navn, JavaScript ikke kender til”.

3. event is not defined

En klassiker når du har skrevet meget inline-kode eller kopieret eksempler.

button.addEventListener('click', () => {
  console.log(event.target); // ReferenceError: event is not defined
});

I moderne JavaScript skal du selv tage imod event som parameter:

button.addEventListener('click', (event) => {
  console.log(event.target);
});

Tjek altid funktionen: står (event) eller (e) der faktisk? Hvis ikke, så ved JavaScript ikke, hvad event er.

4. Variable is not defined

Eksempel:

function saveUser() {
  userName = input.value; // ReferenceError i strict mode
}

I stedet for at lave en global ved et uheld, så deklarer variablen:

function saveUser() {
  const userName = input.value;
}

Hvis fejlen peger på en variabel, du troede fandtes, så tjek:

  • Stavemåde (case sensitive: userNameusername).
  • Om den er deklareret med let/const/var før brug.
  • Om du bruger den uden for det scope, du har deklareret den i.

Hvis du vil nørde lidt mere med scope, har vi også mere grundlæggende artikler om JavaScript variabler og funktioner.

DOM-fejl: querySelector der returnerer null

Næsten alle, der begynder på lidt mere DOM-manipulation, løber ind i:

Cannot read properties of null (reading 'addEventListener')

Typisk fordi:

const button = document.querySelector('.save-button');
button.addEventListener('click', handleSave);

Og querySelector returnerer null når elementet ikke findes.

5. Elementet findes ikke (eller ikke endnu)

Tjek de her ting i rækkefølge:

  • Står klassen/ID’et korrekt i HTML’en?
  • Loader du scriptet før HTML’en er renderet?
  • Har du stavet selektoren korrekt (.class vs #id)?

Hvis du har dit script i <head>, så prøv at flytte det ned før </body> eller pak koden ind i:

document.addEventListener('DOMContentLoaded', () => {
  const button = document.querySelector('.save-button');
  // ...
});

Og igen: log det.

console.log('button:', button);

Hvis der står null i console, er problemet ikke addEventListener. Det er selektoren eller timing.

Events der ikke fyrer: handler kører aldrig

Nogle gange får du ikke en fejl, du får bare ingenting. Du klikker, og der sker nada.

6. Forkert selector eller element

Eksempel jeg selv har siddet med:

// HTML
<button id="save">Gem</button>

// JS
const button = document.querySelector('.save');
button.addEventListener('click', saveUser);

Her er problemet: punktum i selektoren (.save) betyder klasse, ikke ID. Den skal være:

const button = document.querySelector('#save');

Tip: Højreklik på elementet i browseren → Inspect → højreklik på HTML-elementet → Copy → Copy selector. Så kan du se, hvad DevTools selv ville bruge som selector.

7. Event delegation

Hvis du tilføjer elementer dynamisk, skal du ofte lytte på en forælder i stedet.

list.addEventListener('click', (event) => {
  if (event.target.matches('.delete')) {
    handleDelete(event.target.dataset.id);
  }
});

Hvis din handler aldrig kører, så smid et log helt i toppen:

list.addEventListener('click', (event) => {
  console.log('click på list', event.target);
  // ...
});

Ser du aldrig den log, så er det ikke JavaScript’en, men nok det element, du lytter på.

JSON-fejl: parse, stringify og små kommaer

JSON-fejl kommer typisk som:

Unexpected token < in JSON at position 0

eller

Unexpected token , in JSON at position X

8. Du prøver at parse HTML eller tekst som JSON

Hvis du bruger fetch og skriver:

const response = await fetch('/api/user');
const data = JSON.parse(await response.text());

og får Unexpected token <, så er det ofte fordi serveren returnerer HTML (fx en fejlside) i stedet for JSON.

I DevTools → fanen Network → klik på request → se fanen Preview eller Response. Hvis du ser en HTML-side eller en PHP-fejl, er problemet i backend, ikke i din JSON.parse.

Hvis serveren faktisk sender JSON, så lad fetch gøre arbejdet:

const response = await fetch('/api/user');
const data = await response.json();

9. Trailing comma i JSON

I JavaScript objekter må du skrive:

const user = {
  name: 'Mikkel',
  age: 38,
};

Men i JSON må der ikke være et ekstra komma til sidst.

{
  "name": "Mikkel",
  "age": 38
}

Hvis du manuelt skriver JSON (fx til en lokal fil eller en test), så tjek altid for trailing commas. En JSON-validator online kan også være god til lige at bekræfte.

Async-fejl: fetch, await og “Uncaught (in promise)”

Asynkron kode er der, hvor jeg selv stødte panden mest mod bordet. Specielt når der stod Uncaught (in promise) uden så meget andet.

10. Uncaught (in promise)

Hvis du ser noget i stil med:

Uncaught (in promise) TypeError: Failed to fetch

Så betyder det, at en Promise blev rejected, og ingen tog sig af den.

Med async/await vil du ofte skrive:

async function loadUser() {
  const response = await fetch('/api/user');
  const data = await response.json();
  renderUser(data);
}

Det går fint, indtil der kommer en netværksfejl, eller serveren svarer med en 500. Så er det værd at pakke det ind i try/catch:

async function loadUser() {
  try {
    const response = await fetch('/api/user');

    if (!response.ok) {
      throw new Error('Serverfejl: ' + response.status);
    }

    const data = await response.json();
    renderUser(data);
  } catch (error) {
    console.error('Kunne ikke loade bruger', error);
    showErrorMessage('Noget gik galt, prøv igen senere.');
  }
}

Nu får du både en kontrolleret fejl i UI og en mere informativ log.

11. Glemmer at await’e

En mere stille fejl er når du glemmer await.

async function loadUser() {
  const response = fetch('/api/user');
  const data = response.json();
  console.log(data.name); // TypeError: Cannot read properties of undefined
}

Her er response en Promise, ikke det rigtige svar. Same med data.

Rigtig version:

async function loadUser() {
  const response = await fetch('/api/user');
  const data = await response.json();
  console.log(data.name);
}

Hvis du logger noget og ser Promise {<pending>}, er det næsten altid et tegn på, at der mangler et await et sted.

Moduler: import/export og path-problemer

Når man går fra én stor app.js til flere filer og moduler, kommer der nye fejltyper:

Uncaught SyntaxError: Cannot use import statement outside a module

eller

Failed to load module script: Expected a JavaScript module script

12. Husk type=”module” i script-tag

Hvis du vil bruge import/export direkte i browseren, skal dit script markeres som modul:

<script type="module" src="./app.js"></script>

Uden det får du typisk den første fejl ovenfor.

13. Forkert sti i import

Et andet klassisk problem er stier.

// I app.js
import { formatUser } from './utils.js';

Tjek:

  • Hedder filen utils.js (ikke util.js eller utils.mjs)?
  • Er stien relativ til den fil du importerer fra?
  • Bruger du ./ eller ../ det rigtige sted?

Igen, brug Network-fanen: hvis du ser en 404 på en .js-fil, er pathen forkert.

En mini tjekliste: Console + Network i DevTools

For ikke at drukne dig i 100 punkter, har jeg skåret min egen “hjælp, der er en fejl” rutine ned til de her faste ting. Jeg bruger dem mere eller mindre hver gang.

14. Console: de 4 ting jeg altid kigger efter

  • Første fejl øverst efter reload. Senere fejl kan være følgefejl.
  • Fil og linje (fx app.js:42) og klik på den.
  • Fejltypen: TypeError, ReferenceError, SyntaxError, NetworkError osv.
  • Stack trace: klik på de forskellige linjer og se, hvor din kode bliver kaldt fra.

Brug også console.log, console.warn og console.error bevidst. Det er lille, men det gør det nemmere at skelne dine egne logs fra browserens.

15. Network: de 3 hurtigste checks

Når noget med API’er, billeder eller scripts driller, åbner jeg næsten automatisk Network.

  • Status-kode: 200, 404, 500, 301 osv. Fejlen er ofte allerede der.
  • Response: er det faktisk JSON, HTML, en fejlbesked, noget helt fjerde?
  • Preview: giver mening for en bruger, eller er det en stack trace fra serveren?

Hvis du vil øve dig, kan du bygge en lille testside med et par bevidst fejlede fetch-kald og se, hvordan de opfører sig i Network. Det er en kedelig øvelse, men den betaler sig næste gang din “rigtige” app brokker sig.

Fejl er ikke en afvigelse, det er selve JavaScript-arbejdet

Hvis du sidder og tænker, at det virker som meget besvær bare for at få en knap til at gemme noget, så er det en ret normal følelse. Jeg har stadig aftener ved køkkenbordet, hvor jeg stirrer på den samme Uncaught TypeError lidt for længe, mens kaffen bliver kold.

Men hver gang jeg faktisk går systematisk til værks – åbner DevTools, læser fejlen, isolerer linjen, logger målrettet og tjekker Network – så kommer jeg hurtigere igennem. Ikke fordi jeg er blevet klogere, men fordi processen ikke ændrer sig, selv når problemet gør.

Hvis nogen fortæller dig, at “rigtige” udviklere næsten aldrig ser fejl i console, så lyver de. Dygtige udviklere er bare bedre til at udnytte dem.

Og hvis du vil have det næste skridt, så er det at tage en af dine egne små bugs og bevidst køre den igennem den her proces i stedet for at kommentere linjen ud som første refleks. Det er dér, du begynder at arbejde med JavaScript, ikke imod det.

Fejl er ikke støj, de er din vigtigste feedback.

Åbn fanen Sources, klik på linjenummeret for at sætte et breakpoint og genudfør handlingen. Du kan højreklikke for at lave betingede breakpoints, bruge knapperne Step Over / Step Into / Resume til at navigere, og indsætte en debugger; sætning i koden hvis du vil stoppe programmet programmatisk. Når koden er stoppet, kan du inspicere scope-variabler og sætte watch-udtryk.
Sæt breakpoints inde i selve then/catch-blokkene eller i async-funktionen, så du kan ramme koden når callbacken kører. Brug DevTools funktioner som Pause on exceptions og aktiver async call stacks hvis muligt, og log/brug debugger; i catch-blokke for at se den oprindelige fejl og kontekst. Det gør det langt nemmere at følge flowet når ting sker på tværs af opgaver.
Valider input og giv sensible standardværdier tidligt, fx gennem default parametre eller nul-sikring. Brug moderne værktøjer som optional chaining (?.) og nullish coalescing (??) for at undgå at tilgå properties på undefined, og skriv små helper-funktioner til at sikre typer før du bruger et objekt.
Ja - en kombination virker bedst: unit tests til forretningslogik, integration tests til DOM-interaktioner og end-to-end tests til brugerflows. Brug værktøjer som Jest + Testing Library til komponenter og Cypress eller Playwright til end-to-end, og dæk især grænsetilfælde som null/undefined og særlige brugerhandlinger.

Mikkel Schrøder er den dér stille type, der i årevis har siddet om aftenen med en kop kaffe og et åbent kodeprojekt, mens resten af huset er ved at falde til ro. Hans interesse for kodning startede, da han som teenager forsøgte at lave en simpel hjemmeside til sit favorit-fodboldhold og opdagede, at man kunne ændre alt ved at rode med HTML og CSS. Siden har han lært tingene ved at prøve sig frem, læse forumtråde og pille ved små projekter, indtil de gjorde det, han ville.

På Coding Class deler han ikke perfekte løsninger fra et glansbillede-univers, men de ting han faktisk selv har bokset med: mærkelige JavaScript-fejl, CSS der ikke opfører sig som forventet, og små Python-scripts, der starter i kaos og ender med at spare tid i hverdagen. Han kan godt lide at vise både den første, halvdårlige løsning og den forbedrede udgave, så du kan se forskellen og forstå tankegangen bag.

Mikkel brænder for at gøre programmering mindre skræmmende for dem, der ikke ser sig selv som "tech-typer". Derfor skriver han på helt almindeligt dansk, med små, konkrete kodeeksempler og fokus på, hvordan du selv kan komme fra teori til noget, der faktisk virker. På Coding Class forsøger han at bygge bro mellem manual-sproget og virkeligheden ved at vise, hvordan det føles at sidde med fejlen klokken 22.30 – og hvad der skulle til, før den forsvandt.

Send kommentar

You May Have Missed