We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Stop mixing LiveView handlers with JavaScript hooks
almirsarajcic
Handling the same event in both Phoenix and JavaScript creates race conditions and unpredictable behavior. Keep server logic in LiveView handlers and client-only interactions in JavaScript hooks.
# ❌ RACE CONDITION - Both server and client handle the same action
defmodule SearchLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, results: [], query: "")}
end
def handle_event("search", %{"query" => query}, socket) do
results = perform_search(query)
{:noreply, assign(socket, results: results, query: query)}
end
# Server clearing conflicts with JavaScript clearing
def handle_event("clear_search", _params, socket) do
{:noreply, assign(socket, results: [], query: "")}
end
end
// JavaScript ALSO tries to clear - creates race condition
const SearchHook = {
mounted() {
this.el.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.el.value = '' // Client clears input
this.pushEvent('clear_search') // Server ALSO clears
}
})
},
}
The problem: pressing Escape triggers client-side clearing AND server-side clearing. The input flashes empty, then re-renders, creating visual glitches and wasted round trips.
# ✅ CLEAN SEPARATION - Server handles data, client handles UI
defmodule SearchLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, results: [], query: "")}
end
# Server only: Handle search logic
def handle_event("search", %{"query" => query}, socket) do
results = perform_search(query)
{:noreply, assign(socket, results: results, query: query)}
end
end
// Client only: Handle instant UI feedback
const SearchHook = {
mounted() {
this.el.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.el.value = ''
this.pushEvent('search', { query: '' })
}
})
},
}
export default SearchHook
Now pressing Escape clears the input instantly (JavaScript) and notifies the server (LiveView event). No race condition, no double-handling.
When to use JavaScript hooks vs LiveView handlers
JavaScript hooks (client-only):
- Instant UI feedback (focus, blur, animations)
- Keyboard shortcuts (Escape, Ctrl+K)
- DOM manipulation that doesn’t need server knowledge
- Browser APIs (clipboard, local storage, geolocation)
LiveView handlers (server-only):
- Database queries
- Business logic
- Authorization checks
- State management that other LiveViews need to know about
Both together:
- JS hook handles instant client feedback →
pushEvent()
→ LiveView handler processes server logic
This pattern eliminates race conditions and gives you instant client feedback with reliable server processing.
copied to clipboard