Reusable Alpine

Use global stores sparingly

Reserve Alpine.store for genuinely shared browser state and keep component details local.

Alpine.store() creates state that every component on the page can read and change. It’s the escape hatch for values that don’t belong to any one element.

That makes it powerful and dangerous in the same breath. Every value you put in a store can now be mutated from anywhere in the HTML. Use it for the two or three things that are truly page-wide, and nothing else.

What a store looks like

Register it in alpine:init, like a data component:

document.addEventListener('alpine:init', () => {
  Alpine.store('connection', {
    online: navigator.onLine,

    init() {
      window.addEventListener('online', () => this.online = true)
      window.addEventListener('offline', () => this.online = false)
    }
  })
})

Any component reads it through $store:

<p x-show="!$store.connection.online" role="status">
  You're offline. Changes will be saved when you reconnect.
</p>

<button :disabled="!$store.connection.online || saving">Save</button>

The banner at the top of the page and the Save button in every editor read the same value. Unplug the network and both react. That’s a good store: one fact about the browser, many readers, one place that writes it.

What doesn’t belong there

One issue row’s menu being open? Local. The editor’s title? Local. Whether the filter panel is expanded? Local to the panel.

The temptation goes like this. Two components reference the same value, so someone moves it to a store “to share it”. Now a third component starts writing to it. Then a fourth. Six months later, $store.ui.open is set from eleven places and nobody knows which one closed the modal.

Ask who writes

Before promoting a value, list its writers. Not its readers, its writers.

  • one writer, many readers: a store is fine. Connection status, the current theme, the logged-in user’s name.
  • many writers: stop. Something is off in the design. Usually two components are pretending to be one, or the server should own the value.
  • one writer, one reader, different components: try a custom event first. $dispatch('issue-saved', { id: 42 }) from the editor, @issue-saved.window on the list. No shared state at all.

Audit the board

Go through every value on the issue board and put it in a column: server, URL, page-local, component-local, store. My list ends with one store entry, connection, and possibly a second for the toast notifications.

If your list has more than three store entries, look at each one again. One of them is almost certainly a component detail that leaked upward.

Lesson completed