We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Use `send_update_after/4` for delayed LiveComponent updates
almirsarajcic
Manual timer management with Process.send_after/3 in LiveComponents creates cleanup complexity. LiveView’s send_update_after/4 handles delayed updates automatically without timer tracking.
# ✅ BUILT-IN DELAY - Automatic timer management
defmodule NotificationComponent do
use Phoenix.LiveComponent
def render(assigns) do
~H"""
<div :if={@show} class="notification">
<%= @message %>
</div>
"""
end
def update(%{message: message}, socket) do
# Show notification, auto-hide after 5 seconds
send_update_after(__MODULE__, [id: socket.assigns.id, show: false], 5_000)
{:ok, assign(socket, message: message, show: true)}
end
def update(%{show: false}, socket) do
{:ok, assign(socket, show: false)}
end
end
# Usage in parent LiveView
def handle_event("save", params, socket) do
send_update(NotificationComponent, id: "notif", message: "Saved!")
{:noreply, socket}
end
Without send_update_after/4, you need manual timer references and cleanup:
# ❌ MANUAL TIMERS - Complex cleanup required
defmodule NotificationComponent do
use Phoenix.LiveComponent
def render(assigns) do
~H"""
<div :if={@show} class="notification">
<%= @message %>
</div>
"""
end
def update(%{message: message}, socket) do
# Cancel previous timer if exists
if socket.assigns[:timer_ref] do
Process.cancel_timer(socket.assigns.timer_ref)
end
# Set new timer and track reference
timer_ref = Process.send_after(self(), {:hide_notification, socket.assigns.id}, 5_000)
{:ok, assign(socket, message: message, show: true, timer_ref: timer_ref)}
end
# Parent LiveView needs to handle the message
def handle_info({:hide_notification, component_id}, socket) do
send_update(NotificationComponent, id: component_id, show: false)
{:noreply, socket}
end
end
How send_update_after works
send_update_after/4 schedules a component update and returns a cancellable reference:
# Schedule update and get reference
ref = send_update_after(StatusComponent, [id: "status", status: :idle], 10_000)
# Cancel if needed
Process.cancel_timer(ref)
The timer is automatically cleaned up when:
- The LiveView process terminates
- The component is removed from the DOM
- A new update overrides the pending state
Common patterns
Auto-refresh components:
defmodule LiveStatsComponent do
use Phoenix.LiveComponent
def mount(socket) do
# Refresh every 30 seconds
schedule_refresh()
{:ok, assign(socket, stats: load_stats())}
end
def update(assigns, socket) do
if assigns[:refresh] do
schedule_refresh()
{:ok, assign(socket, stats: load_stats())}
else
{:ok, assign(socket, assigns)}
end
end
defp schedule_refresh do
send_update_after(__MODULE__, [id: "stats", refresh: true], 30_000)
end
end
Debounced state changes:
defmodule SearchComponent do
use Phoenix.LiveComponent
def handle_event("search", %{"query" => query}, socket) do
# Update immediately for UI feedback
socket = assign(socket, query: query, searching: true)
# Debounce actual search by 500ms
send_update_after(__MODULE__, [id: socket.assigns.id, execute_search: query], 500)
{:noreply, socket}
end
def update(%{execute_search: query}, socket) do
results = perform_search(query)
{:ok, assign(socket, results: results, searching: false)}
end
end
Parent LiveView triggering delayed updates:
defmodule DashboardLive do
use Phoenix.LiveView
def handle_event("start-background-job", params, socket) do
# Start job immediately
{:ok, job} = start_job(params)
send_update(StatusComponent, id: "status", status: :running)
# Auto-reset status after job completes (estimated 10 seconds)
send_update_after(StatusComponent, [id: "status", status: :idle], 10_000)
{:noreply, assign(socket, current_job: job)}
end
def handle_event("show-temp-message", %{"text" => text}, socket) do
# Show message in component, auto-hide after 3 seconds
send_update(MessageComponent, id: "msg", text: text, visible: true)
send_update_after(MessageComponent, [id: "msg", visible: false], 3_000)
{:noreply, socket}
end
end
Pro tip: Use send_update_after/4 instead of combining Process.send_after/3 with handle_info/2 for cleaner component-scoped delays.
copied to clipboard
Comments (0)
Sign in with GitHub to join the discussion