Debug an Error with AI Using a Reproducible Example

An error message is a clue. Give AI the exact error, the relevant code and the steps that cause it, and it has something useful to work with.

In this tutorial, you will fix a small reading-list app that stops before its Add button works. You will reproduce the problem, identify why a selector returns no element, make a one-line correction and check the result.

Small fixes are easier to check. The missing letter does not need a brand-new app.

What you need

Allow about 35 minutes. You only need to understand that HTML describes page elements and JavaScript adds behaviour. The example has no dependencies, accounts, network requests or data storage. Reloading clears the list.

HTML uses book-form, broken code selects books-form and returns null, and the fix selects book-form.
The deliberate demo bug is a singular/plural mismatch. Check the actual error before changing anything.
Read this example as text

HTML form name

  • id="book-form"
  • The form uses the singular name.

Broken JavaScript

  • #books-form
  • No match → null

Smallest fix

  • #book-form
  • Find the real form, then connect it.

See the finished example

Open the complete example

1. Reproduce the problem before editing

Open broken.html in your browser. You should see a reading-list form. Enter The Hobbit and try Add book. The list does not receive the book because the script stopped while connecting the form handler.

Open your browser's developer tools using its menu, select Console, then reload the page. The error should refer to calling addEventListener on null. Wording and line numbers vary between browsers.

Record these facts:

Expected: submitting a non-empty title adds one book to the list.
Actual: the initial page loads, but submitting does not add a book.
Reproduction: open broken.html, reload with Console open,
type The Hobbit, submit the form.
Error: [COPY THE EXACT MESSAGE FROM YOUR CONSOLE]
Environment: [BROWSER NAME AND VERSION]

Do not invent a console message that you have not seen. If you cannot reproduce a reported issue, say what you tried and what happened instead.

2. Reduce the code you share

The important mismatch is small. The HTML form uses a singular ID:

<form id="book-form">

The broken JavaScript searches for a plural ID:

const form = document.querySelector('#books-form');
form.addEventListener('submit', handleSubmit);

No element has the ID books-form. querySelector() therefore returns null, and the next line tries to call a method on a missing element. This return behaviour is defined in MDN's querySelector documentation.

The script in the companion example is placed after the form. That matters: a correct selector can also return null if a script runs before the element exists. Here, the evidence points to the spelling mismatch rather than loading order.

3. Ask for a diagnosis that can be checked

Copy the exact error and the relevant HTML and JavaScript into this prompt. You can supply the complete small example if needed; do not send unrelated project files, passwords or private user data.

Help me diagnose this browser JavaScript error.

Expected behaviour: [WHAT SHOULD HAPPEN]
Actual behaviour: [WHAT HAPPENS]
Reproduction steps: [NUMBERED STEPS]
Browser/version: [ENVIRONMENT]
Exact error: [PASTE THE ERROR]

Relevant HTML:
[PASTE THE FORM AND INPUT]

Relevant JavaScript:
[PASTE THE SELECTOR, HANDLER AND EVENT REGISTRATION]

Explain the most likely cause using the supplied evidence.
Give me one check I can perform before changing the code.
Then suggest the smallest fix. If evidence is missing, ask for it.
Do not rewrite the app or add libraries.

For this example, an diagnosis supported by the error and code should connect #books-form to the absence of a matching form. A confident explanation that ignores the supplied HTML is not enough.

4. Check the hypothesis in the Console

In your local demo's Console, run these two expressions separately:

document.querySelector('#books-form')
document.querySelector('#book-form')

The first should return null; the second should identify the actual form element. You are checking the specific claim before accepting a repair.

Only run code you understand in the Console. These expressions inspect your own local demo; they do not send or change data.

5. Make the smallest correction

Change the selector to match the HTML:

- const form = document.querySelector('#books-form');
+ const form = document.querySelector('#book-form');

Save and reload. The fixed.html companion contains the same example with that one selector corrected.

A tempting alternative is to wrap the event registration in if (form). That would avoid the crash but leave the intended form disconnected. A guard can be useful when an element is genuinely optional; it does not solve this ID mismatch.

If an assistant proposes a large rewrite, use a focused follow-up:

Keep the existing HTML, event handler and file structure.
The supplied HTML has id="book-form", while the JavaScript selects
"#books-form". Show the smallest correction and explain why hiding
the error with a null guard would not restore the intended behaviour.

6. Check behaviour, not just the absence of errors

Try the following in the fixed version:

Check Expected result
Add The Hobbit One list item appears and the input clears.
Add Dune Both items remain in the list.
Submit an empty title No item is added; a useful message appears.
Submit spaces only No item is added.
Add a title with spaces around it Leading and trailing spaces are removed.
Submit using Enter in the input The same form action works.
Add <b>example</b> The characters appear as text, not bold markup.
Reload The list clears; persistence is outside this demo.

The handler uses textContent for the entered title rather than treating it as HTML. That makes the final check meaningful.

The folder also includes verify.mjs, an automated check of the example script using a small simulated DOM. If Node.js is available, run this from the debugging-demo folder:

node verify.mjs

It checks that the broken version fails at event registration, that the fixed version registers the form handler, and that sample inputs behave as intended. A simulated DOM is not a browser: it does not verify visual layout, real keyboard events or browser-specific console wording. Those checks still belong in the browser.

7. Save the useful debugging record

Keep a short note that explains the cause, not just the edited line:

Cause: JavaScript selected #books-form, but the HTML form ID was book-form.
Evidence: the plural selector returned null; the singular selector matched.
Fix: changed the selector to #book-form.
Checks: [RECORD THE CASES YOU ACTUALLY RAN AND THEIR RESULTS]
Remaining limits: no data persistence; browser-specific checks recorded separately.

This record helps if the bug returns or another person reviews the change.

Common problems

The selector is correct but still returns null. Check whether the script runs before the element exists, whether you are in the right document, and whether the element is created later. Do not assume this tutorial's cause applies to every null error.

The error disappeared but Add still does nothing. Check whether the handler was connected at all. A conditional that silently skips registration can hide the symptom.

My changes do not appear. Confirm that you saved the correct file, opened that file and reloaded it. In a served project, caching may also matter.

The AI asks for the entire project. Start with the exact error, the relevant element, the selector, the handler and the reproduction steps. Expand the example only if those are not enough.

The proposed fix creates a different failure. Keep the original reproduction, compare the change, and test one hypothesis at a time. An AI-generated answer is a suggestion until the evidence supports it.

What to try next

Introduce a different deliberate bug in a copy of the demo, such as changing the input selector. Reproduce it, record the evidence and use the same process. Keep the working version available for comparison.

For more on reading errors and distinguishing syntax problems from logic problems, see MDN's JavaScript troubleshooting guide.

Complete local demo files

Save each example as the indicated HTML file on your own computer. These are tutorial source examples, not code to run inside WordPress.

broken.html

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Reading list — deliberate bug</title>
<style>
body{font:18px/1.6 system-ui,sans-serif;background:#faf7f0;color:#173e56;margin:0;padding:32px}main{max-width:660px;margin:40px auto;background:white;padding:32px;border-radius:24px}label{display:block;font-weight:700}input,button{font:inherit;padding:12px;border:2px solid #173e56;border-radius:10px}input{max-width:100%;box-sizing:border-box}button{background:#173e56;color:white;cursor:pointer}li{padding:8px 0}a{color:#176d73}
</style>
<main>
<h1>My reading list</h1>
<p>This version contains a deliberate selector bug. Open the Console, then reload.</p>
<form id="book-form" novalidate>
<label for="book-title">Book title</label>
<input id="book-title" type="text" autocomplete="off">
<button type="submit">Add book</button>
</form>
<p id="status" role="status" aria-live="polite"></p>
<ul id="book-list" aria-label="Books to read"></ul>
<p>Reloading clears this demo. <a href="fixed.html">Open the fixed version</a>.</p>
</main>
<script>
const form = document.querySelector('#books-form');
const input = document.querySelector('#book-title');
const list = document.querySelector('#book-list');
const status = document.querySelector('#status');

function handleSubmit(event) {
  event.preventDefault();
  const title = input.value.trim();
  if (!title) {
    status.textContent = 'Enter a book title first.';
    input.focus();
    return;
  }
  const item = document.createElement('li');
  item.textContent = title;
  list.appendChild(item);
  input.value = '';
  status.textContent = 'Added: ' + title;
  input.focus();
}

form.addEventListener('submit', handleSubmit);
</script>
</html>

fixed.html

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Reading list — fixed</title>
<style>
body{font:18px/1.6 system-ui,sans-serif;background:#faf7f0;color:#173e56;margin:0;padding:32px}main{max-width:660px;margin:40px auto;background:white;padding:32px;border-radius:24px}label{display:block;font-weight:700}input,button{font:inherit;padding:12px;border:2px solid #173e56;border-radius:10px}input{max-width:100%;box-sizing:border-box}button{background:#173e56;color:white;cursor:pointer}li{padding:8px 0}a{color:#176d73}
</style>
<main>
<h1>My reading list</h1>
<p>This version corrects the form selector. Add a title to try it.</p>
<form id="book-form" novalidate>
<label for="book-title">Book title</label>
<input id="book-title" type="text" autocomplete="off">
<button type="submit">Add book</button>
</form>
<p id="status" role="status" aria-live="polite"></p>
<ul id="book-list" aria-label="Books to read"></ul>
<p>Reloading clears this demo. <a href="broken.html">Compare the broken version</a>.</p>
</main>
<script>
const form = document.querySelector('#book-form');
const input = document.querySelector('#book-title');
const list = document.querySelector('#book-list');
const status = document.querySelector('#status');

function handleSubmit(event) {
  event.preventDefault();
  const title = input.value.trim();
  if (!title) {
    status.textContent = 'Enter a book title first.';
    input.focus();
    return;
  }
  const item = document.createElement('li');
  item.textContent = title;
  list.appendChild(item);
  input.value = '';
  status.textContent = 'Added: ' + title;
  input.focus();
}

form.addEventListener('submit', handleSubmit);
</script>
</html>

Scroll to Top