Local state and directives

Inspect Alpine state

Use browser tools and small expressions to trace scope, initialization, and DOM changes.

Alpine is JavaScript changing the DOM. Nothing magic. So when something doesn’t work, the normal browser tools work too. Console, Elements panel, event listeners.

Here are the four things I do, in order, when an Alpine component misbehaves.

Read the console first

Alpine logs expression errors to the console with the element that caused them. A typo like x-text="qeury" produces:

Alpine Expression Error: qeury is not defined
Expression: "qeury"
<span x-text="qeury"></span>

That’s usually enough. The message tells you the name, and the element tells you the scope to look at.

Read the state from the console

Select an element in the Elements panel. The console now refers to it as $0. Ask Alpine for its data:

Alpine.$data($0)

You get the reactive object for that scope. Change a value in the console and the page updates:

Alpine.$data($0).expanded = true

If a value you expect isn’t there, you selected an element outside the scope. That’s the shadowing problem from the previous lesson, made visible.

Watch a value change

When state changes at the wrong time, I add a temporary watcher. $watch takes a property name and a callback, and x-init runs code once when the component starts:

<form x-data="{ query: '', status: '' }"
  x-init="$watch('query', value => console.log('query:', value))">

Type in the search box and the console prints every change:

query: l
query: lo
query: log

Now you know the filter state moves. If the list doesn’t, the problem is in the list expression, not the input.

Check the DOM Alpine produced

The last step is the Elements panel. Look at the actual attributes. Is aria-expanded really "true"? Did x-cloak get removed? Is style="display: none;" there when you expect it?

The DOM is the output. If the state is right and the DOM is wrong, the directive is wrong.

Don’t add directives until it works

The trap is piling on more x-show, more x-init, more handlers, until the interface works by accident. Every extra directive is one more thing to debug next month.

Do the opposite. Cut the component down to the one binding that fails. Find the owning scope. Log the transition. Look at the DOM. Then remove the logging.

Try it: break one binding name on purpose in your board, find it with these four steps, then delete the $watch you added. Debugging code that ships is a bug too.

Lesson completed