Skip to content

Instantly share code, notes, and snippets.

@mcrumm
Created October 28, 2025 22:00
Show Gist options
  • Select an option

  • Save mcrumm/24f353b9e81a7aeb9356a270d86882e6 to your computer and use it in GitHub Desktop.

Select an option

Save mcrumm/24f353b9e81a7aeb9356a270d86882e6 to your computer and use it in GitHub Desktop.
Ticking apparatus for Phoenix LiveView.
defmodule Ticking do
@moduledoc ~S'''
Ticking apparatus for Phoenix LiveView.
This version is mainly for testing LiveView operations that may be affected by change tracking.
## Usage
In your LiveView module, add `use Ticking`. Then add the `@ticking` assigns in your template to
force a page update on the tick interval.
```elixir
defmodule MyLive.Example do
use Phoenix.LiveView
use Ticking
def render(assigns) do
~H"""
<p>Hello, world! Ticking time: <%= @ticking %></p>
"""
end
end
```
### Custom Timing
The default interval is `5_000` milliseconds and can be modified using the `:time` option:
```elixir
defmodule MyLive.FastExample do
use Phoenix.LiveView
use Ticking, time: 500
def render(assigns) do
~H"""
<p>I tick fast: <%= @ticking %></p>
"""
end
end
'''
defmacro __using__(opts \\ []) do
time = Keyword.get(opts, :time, 5_000)
quote do
require Phoenix.LiveView
Phoenix.LiveView.on_mount({Ticking, unquote(time)})
def handle_info({__MODULE__, Ticking}, socket) do
{:noreply, Ticking.update(socket, unquote(time))}
end
end
end
alias Phoenix.Component
alias Phoenix.LiveView.Socket
def on_mount(time, _params, _session, socket) do
socket =
socket
|> Component.assign(ticking: nil, ticking_ref: nil)
|> Phoenix.LiveView.put_private(Ticking, time)
|> Phoenix.LiveView.attach_hook(Ticking, :handle_params, &Ticking.on_handle_params/3)
{:cont, socket}
end
def on_handle_params(_params, _uri, socket) do
{:cont, Ticking.update(socket, ticking_time(socket))}
end
def cancel(%Socket{} = socket) do
update(socket, false)
end
def update(%Socket{} = socket, time \\ 5_000) do
socket
|> Component.update(:ticking, fn _ -> System.system_time() end)
|> Component.update(:ticking_ref, fn ref ->
if ref, do: Process.cancel_timer(ref)
if time, do: Process.send_after(socket.root_pid, {socket.view, Ticking}, time)
end)
end
defp ticking_time(%Socket{} = socket) do
Map.fetch!(socket.private, Ticking)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment