# LiveViewBus
[](https://hex.pm/packages/live_view_bus)
[](https://hex.pm/packages/live_view_bus)
[](https://github.com/half-blood-labs/live_view_bus/actions/workflows/ci.yml)
A simple event bus for Phoenix LiveViews to send events between LiveViews and LiveComponents by ID—without manually wiring PubSub, topics, and `handle_info` every time.
## Installation
Add `live_view_bus` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:live_view_bus, "~> 0.1.0"}
]
end
```
## Quick start
### 1. Subscribe in mount
```elixir
defmodule MyAppWeb.ProductLive.Index do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
LiveViewBus.subscribe(MyApp.PubSub, "products_list")
{:ok, assign(socket, products: [], filters: %{})}
end
def handle_info({:live_view_bus, "products_list", "filters_changed", payload}, socket) do
products = load_products(payload)
{:noreply, assign(socket, products: products)}
end
end
```
### 2. Send from another LiveView or Component
```elixir
# When filters change in a sidebar component:
LiveViewBus.send(MyApp.PubSub, "products_list", "filters_changed", %{
category: "electronics",
sort: "price"
})
```
That's it. No PubSub topic management, no boilerplate.
## Usage
### Using the on_mount hook
For automatic subscription when the LiveView mounts, use the `on_mount` hook in your router:
```elixir
# In your router (lib/my_app_web/router.ex)
live_session :default, on_mount: [
{LiveViewBus.LiveView, :subscribe, pubsub: MyApp.PubSub, ids: ["products_list"]}
] do
live "/products", ProductLive.Index
end
```
For multiple IDs:
```elixir
on_mount: [
{LiveViewBus.LiveView, :subscribe, pubsub: MyApp.PubSub, ids: ["products_list", "dashboard"]}
]
```
### Message format
All messages are delivered as:
```elixir
{:live_view_bus, id, event, payload}
```
- `id` – the topic ID you subscribed to
- `event` – the event name (string or atom)
- `payload` – the payload map (defaults to `%{}`)
### API reference
| Function | Description |
|----------|-------------|
| `LiveViewBus.subscribe(pubsub, id)` | Subscribe the current process to `id` |
| `LiveViewBus.unsubscribe(pubsub, id)` | Unsubscribe from `id` |
| `LiveViewBus.send(pubsub, id, event, payload \\ %{})` | Send event to all subscribers of `id` |
| `LiveViewBus.broadcast(pubsub, id, event, payload \\ %{})` | Same as `send/4` |
| `LiveViewBus.topic(id)` | Returns the PubSub topic string (for advanced use) |
> **Note:** Unsubscribe is optional. When the LiveView process ends, PubSub automatically removes the subscription.
## Examples
### Example 1: Filter sidebar + product list
**Filter component** (sends when user changes filters):
```elixir
defmodule MyAppWeb.ProductLive.FilterComponent do
use MyAppWeb, :live_component
def handle_event("apply", %{"category" => category, "sort" => sort}, socket) do
LiveViewBus.send(MyApp.PubSub, "products_list", "filters_changed", %{
category: category,
sort: sort
})
{:noreply, socket}
end
end
```
**Product list LiveView** (receives and updates):
```elixir
defmodule MyAppWeb.ProductLive.Index do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
LiveViewBus.subscribe(MyApp.PubSub, "products_list")
{:ok, assign(socket, products: load_products(%{}))}
end
def handle_info({:live_view_bus, "products_list", "filters_changed", payload}, socket) do
products = load_products(payload)
{:noreply, assign(socket, products: products)}
end
defp load_products(filters) do
# Your Ecto query here
[]
end
end
```
### Example 2: Dashboard with multiple widgets
**Widget A** broadcasts when data is refreshed:
```elixir
LiveViewBus.broadcast(MyApp.PubSub, "dashboard", "data_refreshed", %{
widget: "sales",
timestamp: DateTime.utc_now()
})
```
**Widget B and C** subscribe and react:
```elixir
def mount(_params, _session, socket) do
LiveViewBus.subscribe(MyApp.PubSub, "dashboard")
{:ok, socket}
end
def handle_info({:live_view_bus, "dashboard", "data_refreshed", payload}, socket) do
# Refresh this widget's data
{:noreply, assign(socket, data: fetch_data())}
end
```
### Example 3: Real-time room updates
```elixir
# User joins room "room_123"
def mount(%{"room_id" => room_id}, _session, socket) do
LiveViewBus.subscribe(MyApp.PubSub, "room:#{room_id}")
{:ok, assign(socket, room_id: room_id)}
end
# Another user sends a message:
LiveViewBus.send(MyApp.PubSub, "room:#{room_id}", "new_message", %{
user: "alice",
body: "Hello!"
})
# All subscribers receive it:
def handle_info({:live_view_bus, _topic, "new_message", payload}, socket) do
{:noreply, assign(socket, messages: [payload | socket.assigns.messages])}
end
```
## License
MIT