Phoenix.LiveView behaviour

LiveView provides rich, real-time user experiences with server-rendered HTML.

The LiveView programming model is declarative: instead of saying "once event X happens, change Y on the page", events in LiveView are regular messages which may cause changes to its state. Once the state changes, LiveView will re-render the relevant parts of its HTML template and push it to the browser, which updates itself in the most efficient manner. This means developers write LiveView templates as any other server-rendered HTML and LiveView does the hard work of tracking changes and sending the relevant diffs to the browser.

At the end of the day, a LiveView is nothing more than a process that receives events as messages and updates its state. The state itself is nothing more than functional and immutable Elixir data structures. The events are either internal application messages (usually emitted by Phoenix.PubSub) or sent by the client/browser.

LiveView is first rendered statically as part of regular HTTP requests, which provides quick times for "First Meaningful Paint", in addition to helping search and indexing engines. Then a persistent connection is established between client and server. This allows LiveView applications to react faster to user events as there is less work to be done and less data to be sent compared to stateless requests that have to authenticate, decode, load, and encode data on every request. The flipside is that LiveView uses more memory on the server compared to stateless requests.

Use cases

There are many use cases where LiveView is an excellent fit right now:

  • Handling of user interaction and inputs, buttons, and forms - such as input validation, dynamic forms, autocomplete, etc;

  • Events and updates pushed by server - such as notifications, dashboards, etc;

  • Page and data navigation - such as navigating between pages, pagination, etc can be built with LiveView using the excellent live navigation feature set. This reduces the amount of data sent over the wire, gives developers full control over the LiveView life-cycle, while controlling how the browser tracks those changes in state;

There are also use cases which are a bad fit for LiveView:

  • Animations - animations, menus, and general events that do not need the server in the first place are a bad fit for LiveView, as they can be achieved purely with CSS and/or CSS transitions;

Life-cycle

A LiveView begins as a regular HTTP request and HTML response, and then upgrades to a stateful view on client connect, guaranteeing a regular HTML page even if JavaScript is disabled. Any time a stateful view changes or updates its socket assigns, it is automatically re-rendered and the updates are pushed to the client.

You begin by rendering a LiveView typically from your router. When LiveView is first rendered, the mount/3 callback is invoked with the current params, the current session and the LiveView socket. As in a regular request, params contains public data that can be modified by the user. The session always contains private data set by the application itself. The mount/3 callback wires up socket assigns necessary for rendering the view. After mounting, render/1 is invoked and the HTML is sent as a regular HTML response to the client.

After rendering the static page, LiveView connects from the client to the server where stateful views are spawned to push rendered updates to the browser, and receive client events via phx- bindings. Just like the first rendering, mount/3 is invoked with params, session, and socket state, where mount assigns values for rendering. However in the connected client case, a LiveView process is spawned on the server, pushes the result of render/1 to the client and continues on for the duration of the connection. If at any point during the stateful life-cycle a crash is encountered, or the client connection drops, the client gracefully reconnects to the server, calling mount/3 once again.

Example

Before writing your first example, make sure that Phoenix LiveView is properly installed. If you are just getting started, this can be easily done by running mix phx.new my_app --live. The phx.new command with the --live flag will create a new project with LiveView installed and configured. Otherwise, please follow the steps in the installation guide before continuing.

A LiveView is a simple module that requires two callbacks: mount/3 and render/1:

defmodule MyAppWeb.ThermostatLive do
  # If you generated an app with mix phx.new --live,
  # the line below would be: use MyAppWeb, :live_view
  use Phoenix.LiveView

  def render(assigns) do
    ~L"""
    Current temperature: <%= @temperature %>
    """
  end

  def mount(_params, %{"current_user_id" => user_id}, socket) do
    temperature = Thermostat.get_user_reading(user_id)
    {:ok, assign(socket, :temperature, temperature)}
  end
end

The render/1 callback receives the socket.assigns and is responsible for returning rendered content. You can use Phoenix.LiveView.Helpers.sigil_L/2 to inline LiveView templates.

Next, decide where you want to use your LiveView.

You can serve the LiveView directly from your router (recommended):

defmodule MyAppWeb.Router do
  use Phoenix.Router
  import Phoenix.LiveView.Router

  scope "/", MyAppWeb do
    live "/thermostat", ThermostatLive
  end
end

Note: the above assumes there is plug :put_root_layout call in your router that configures the LiveView layout. This call is automatically included by mix phx.new --live and described in the installation guide. If you don't want to configure a root layout, you must pass layout: {MyAppWeb.LayoutView, "app.html"} as an option to the live macro above.

Alternatively, you can live_render from any template:

<h1>Temperature Control</h1>
<%= live_render(@conn, MyAppWeb.ThermostatLive) %>

Or you can live_render your view from any controller:

defmodule MyAppWeb.ThermostatController do
  ...
  import Phoenix.LiveView.Controller

  def show(conn, %{"id" => id}) do
    live_render(conn, MyAppWeb.ThermostatLive)
  end
end

When a LiveView is rendered, all of the data currently stored in the connection session (see Plug.Conn.get_session/1) will be given to the LiveView.

It is also possible to pass additional session information to the LiveView through a session parameter:

# In the router
live "/thermostat", ThermostatLive, session: %{"extra_token" => "foo"}

# In a view
<%= live_render(@conn, MyAppWeb.ThermostatLive, session: %{"extra_token" => "foo"}) %>

Notice the :session uses string keys as a reminder that session data is serialized and sent to the client. So you should always keep the data in the session to a minimum. For example, instead of storing a User struct, you should store the "user_id" and load the User when the LiveView mounts.

Once the LiveView is rendered, a regular HTML response is sent. In your app.js file, you should find the following:

import {Socket} from "phoenix"
import LiveSocket from "phoenix_live_view"

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
liveSocket.connect()

After the client connects, mount/3 will be invoked inside a spawned LiveView process. At this point, you can use connected?/1 to conditionally perform stateful work, such as subscribing to pubsub topics, sending messages, etc. For example, you can periodically update a LiveView with a timer:

defmodule DemoWeb.ThermostatLive do
  use Phoenix.LiveView
  ...

  def mount(_params, %{"current_user_id" => user_id}, socket) do
    if connected?(socket), do: Process.send_after(self(), :update, 30000)

    case Thermostat.get_user_reading(user_id) do
      {:ok, temperature} ->
        {:ok, assign(socket, temperature: temperature, user_id: user_id)}

      {:error, _reason} ->
        {:ok, redirect(socket, to: "/error")}
    end
  end

  def handle_info(:update, socket) do
    Process.send_after(self(), :update, 30000)
    {:ok, temperature} = Thermostat.get_reading(socket.assigns.user_id)
    {:noreply, assign(socket, :temperature, temperature)}
  end
end

We used connected?(socket) on mount to send our view a message every 30s if the socket is in a connected state. We receive the :update message in the handle_info/2 callback, just like in an Elixir GenServer, and update our socket assigns. Whenever a socket's assigns change, render/1 is automatically invoked, and the updates are sent to the client.

Colocating templates

In the examples above, we have placed the template directly inside the LiveView:

defmodule MyAppWeb.ThermostatLive do
  use Phoenix.LiveView

  def render(assigns) do
    ~L"""
    Current temperature: <%= @temperature %>
    """
  end

For larger templates, you can place them in a file in the same directory and same name as the LiveView. For example, if the file above is placed at lib/my_app_web/live/thermostat_live.ex, you can also remove the render/1 definition above and instead put the template code at lib/my_app_web/live/thermostat_live.html.leex.

Alternatively, you can keep the render/1 callback but delegate to an existing Phoenix.View module in your application. For example:

defmodule MyAppWeb.ThermostatLive do
  use Phoenix.LiveView

  def render(assigns) do
    Phoenix.View.render(MyAppWeb.PageView, "page.html", assigns)
  end
end

In all cases, each assign in the template will be accessible as @assign. You can learn more about assigns and LiveEEx templates in their own guide.

Bindings

Phoenix supports DOM element bindings for client-server interaction. For example, to react to a click on a button, you would render the element:

<button phx-click="inc_temperature">+</button>

Then on the server, all LiveView bindings are handled with the handle_event callback, for example:

def handle_event("inc_temperature", _value, socket) do
  {:ok, new_temp} = Thermostat.inc_temperature(socket.assigns.id)
  {:noreply, assign(socket, :temperature, new_temp)}
end
Binding Attributes
Params phx-value-*
Click Events phx-capture-click, phx-click
Focus/Blur Events phx-window-focus, phx-window-blur, phx-focus, phx-blur
Key Events phx-window-keyup, phx-window-keydown, phx-keyup, phx-keydown
Form Events phx-auto-recover, phx-trigger-action, phx-disable-with, phx-feedback-for, phx-submit, phx-change
Rate Limiting phx-throttle, phx-debounce
DOM Patching phx-update
JS Interop phx-hook

Compartmentalizing markup and events with render, live_render, and live_component

We can render another template directly from a LiveView template by simply calling render:

render SomeView, "child_template.html", assigns

Where SomeView is a regular Phoenix.View, typically defined in lib/my_app_web/views/some_view.ex and "child_template.html" is defined at lib/my_app_web/templates/some_view/child_template.html.leex. As long as the template has the .leex extension and all assigns are passed, LiveView change tracking will also work across templates.

When rendering a child template, any of the phx-* events in the child template will be sent to the LiveView. In other words, similar to regular Phoenix templates, a regular render call does not start another LiveView. This means render is useful for sharing markup between views.

If you want to start a separate LiveView from within a LiveView, then you can call live_render/3 instead of render/3. This child LiveView runs in a separate process than the parent, with its own mount and handle_event callbacks. If a child LiveView crashes, it won't affect the parent. If the parent crashes, all children are terminated.

When rendering a child LiveView, the :id option is required to uniquely identify the child. A child LiveView will only ever be rendered and mounted a single time, provided its ID remains unchanged. Updates to a child session will be merged on the client, but not passed back up until either a crash and re-mount or a connection drop and recovery. To force a child to re-mount with new session data, a new ID must be provided.

Given that a LiveView runs on its own process, it is an excellent tool for creating completely isolated UI elements, but it is a slightly expensive abstraction if all you want is to compartmentalize markup and events. For example, if you are showing a table with all users in the system, and you want to compartmentalize this logic, rendering a separate LiveView for each user, then using a process per user would likely be too expensive. For these cases, LiveView provides Phoenix.LiveComponent, which are rendered using live_component/3:

<%= live_component(@socket, UserComponent, id: user.id, user: user) %>

Components have their own mount and handle_event callbacks, as well as their own state with change tracking support. Components are also lightweight as they "run" in the same process as the parent LiveView. However, this means an error in a component would cause the whole view to fail to render. See Phoenix.LiveComponent for a complete rundown on components.

To sum it up:

  • render - compartmentalizes markup
  • live_component - compartmentalizes state, markup, and events
  • live_render - compartmentalizes state, markup, events, and error isolation

Endpoint configuration

LiveView accepts the following configuration in your endpoint under the :live_view key:

  • :signing_salt (required) - the salt used to sign data sent to the client

  • :hibernate_after (optional) - the idle time in milliseconds allowed in the LiveView before compressing its own memory and state. Defaults to 15000ms (15 seconds)

Guides

LiveView has many guides to help you on your journey.

Server-side

These guides focus on server-side functionality:

Client-side

These guides focus on LiveView bindings and client-side integration:

Summary

Types

Functions

__using__(opts)

Uses LiveView in the current module to mark it a LiveView.

assign(socket, key, value)

Adds key value pairs to socket assigns.

assign_new(socket, key, func)

Assigns a value into the socket only if it does not exist.

clear_flash(socket)

Clears the flash.

clear_flash(socket, key)

Clears a key from the flash.

connected?(socket)

Returns true if the socket is connected.

get_connect_info(socket)

Accesses the connect info from the socket to use on connected mount.

get_connect_params(socket)

Accesses the connect params sent by the client for use on connected mount.

push_event(socket, event, payload)

Pushes an event to the client to be consumed by hooks.

push_patch(socket, opts)

Annotates the socket for navigation within the current LiveView.

push_redirect(socket, opts)

Annotates the socket for navigation to another LiveView.

put_flash(socket, kind, msg)

Adds a flash message to the socket to be displayed.

redirect(socket, arg2)

Annotates the socket for redirect to a destination path.

send_update(module, assigns)

Asynchronously updates a Phoenix.LiveComponent with new assigns.

send_update_after(module, assigns, time_in_milliseconds)

Similar to send_update/2 but the update will be delayed according to the given time_in_milliseconds.

static_changed?(socket)

Returns true if the socket is connected and the tracked static assets have changed.

transport_pid(socket)

Returns the transport pid of the socket.

update(socket, key, func)

Updates an existing key in the socket assigns.

Callbacks

mount(arg1, session, socket)

The LiveView entry-point.

Types

unsigned_params()

Specs

unsigned_params() :: map()

Functions

__using__(opts)

Uses LiveView in the current module to mark it a LiveView.

use Phoenix.LiveView,
  namespace: MyAppWeb,
  container: {:tr, class: "colorized"},
  layout: {MyAppWeb.LayoutView, "live.html"}

Options

  • :namespace - configures the namespace the LiveView is in
  • :container - configures the container the LiveView will be wrapped in
  • :layout - configures the layout the LiveView will be rendered in

assign(socket, attrs)

See assign/3.

assign(socket, key, value)

Adds key value pairs to socket assigns.

A single key value pair may be passed, or a keyword list or a map of assigns may be provided to be merged into existing socket assigns.

Examples

iex> assign(socket, :name, "Elixir")
iex> assign(socket, name: "Elixir", logo: "????")
iex> assign(socket, %{name: "Elixir"})

assign_new(socket, key, func)

Assigns a value into the socket only if it does not exist.

Useful for lazily assigning values and referencing parent assigns.

Referencing parent assigns

When a LiveView is mounted in a disconnected state, the Plug.Conn assigns will be available for reference via assign_new/3, allowing assigns to be shared for the initial HTTP request. The Plug.Conn assigns will not be available during the connected mount. Likewise, nested LiveView children have access to their parent's assigns on mount using assign_new, which allows assigns to be shared down the nested LiveView tree.

Examples

# controller
conn
|> assign(:current_user, user)
|> LiveView.Controller.live_render(MyLive, session: %{"user_id" => user.id})

# LiveView mount
def mount(_params, %{"user_id" => user_id}, socket) do
  {:ok, assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)}
end

clear_flash(socket)

Clears the flash.

Examples

iex> clear_flash(socket)

clear_flash(socket, key)

Clears a key from the flash.

Examples

iex> clear_flash(socket, :info)

connected?(socket)

Returns true if the socket is connected.

Useful for checking the connectivity status when mounting the view. For example, on initial page render, the view is mounted statically, rendered, and the HTML is sent to the client. Once the client connects to the server, a LiveView is then spawned and mounted statefully within a process. Use connected?/1 to conditionally perform stateful work, such as subscribing to pubsub topics, sending messages, etc.

Examples

defmodule DemoWeb.ClockLive do
  use Phoenix.LiveView
  ...
  def mount(_params, _session, socket) do
    if connected?(socket), do: :timer.send_interval(1000, self(), :tick)

    {:ok, assign(socket, date: :calendar.local_time())}
  end

  def handle_info(:tick, socket) do
    {:noreply, assign(socket, date: :calendar.local_time())}
  end
end

get_connect_info(socket)

Accesses the connect info from the socket to use on connected mount.

Connect info are only sent when the client connects to the server and only remain available during mount. nil is returned when called in a disconnected state and a RuntimeError is raised if called after mount.

Examples

First, when invoking the LiveView socket, you need to declare the connect_info you want to receive. Typically, it includes at least the session but it may include other keys, such as :peer_data. See Phoenix.Endpoint.socket/3:

socket "/live", Phoenix.LiveView.Socket,
  websocket: [connect_info: [:peer_data, session: @session_options]]

Those values can now be accessed on the connected mount as get_connect_info/1:

def mount(_params, _session, socket) do
  if info = get_connect_info(socket) do
    {:ok, assign(socket, ip: info.peer_data.address)}
  else
    {:ok, assign(socket, ip: nil)}
  end
end

get_connect_params(socket)

Accesses the connect params sent by the client for use on connected mount.

Connect params are only sent when the client connects to the server and only remain available during mount. nil is returned when called in a disconnected state and a RuntimeError is raised if called after mount.

Reserved params

The following params have special meaning in LiveView:

  • "_csrf_token" - the CSRF Token which must be explicitly set by the user when connecting
  • "_mounts" - the number of times the current LiveView is mounted. It is 0 on first mount, then increases on each reconnect. It resets when navigating away from the current LiveView or on errors
  • "_track_static" - set automatically with a list of all href/src from tags with the "phx-track-static" annotation in them. If there are no such tags, nothing is sent

Examples

def mount(_params, _session, socket) do
  {:ok, assign(socket, width: get_connect_params(socket)["width"] || @width)}
end

push_event(socket, event, payload)

Pushes an event to the client to be consumed by hooks.

Note: events will be dispatched to all active hooks on the client who are handling the given event. Scoped events can be achieved by namespacing your event names.

Examples

{:noreply, push_event(socket, "scores", %{points: 100, user: "josé"})}

push_patch(socket, opts)

Annotates the socket for navigation within the current LiveView.

When navigating to the current LiveView, handle_params/3 is immediately invoked to handle the change of params and URL state. Then the new state is pushed to the client, without reloading the whole page while also maintaining the current scroll position. For live redirects to another LiveView, use push_redirect/2.

Options

  • :to - the required path to link to. It must always be a local path
  • :replace - the flag to replace the current history or push a new state. Defaults false.

Examples

{:noreply, push_patch(socket, to: "/")}
{:noreply, push_patch(socket, to: "/", replace: true)}

push_redirect(socket, opts)

Annotates the socket for navigation to another LiveView.

The current LiveView will be shutdown and a new one will be mounted in its place, without reloading the whole page. This can also be used to remount the same LiveView, in case you want to start fresh. If you want to navigate to the same LiveView without remounting it, use push_patch/2 instead.

Options

  • :to - the required path to link to. It must always be a local path
  • :replace - the flag to replace the current history or push a new state. Defaults false.

Examples

{:noreply, push_redirect(socket, to: "/")}
{:noreply, push_redirect(socket, to: "/", replace: true)}

put_flash(socket, kind, msg)

Adds a flash message to the socket to be displayed.

Note: While you can use put_flash/3 inside a Phoenix.LiveComponent, components have their own @flash assigns. The @flash assign in a component is only copied to its parent LiveView if the component calls push_redirect/2 or push_patch/2.

Note: You must also place the Phoenix.LiveView.Router.fetch_live_flash/2 plug in your browser's pipeline in place of fetch_flash to be supported, for example:

import Phoenix.LiveView.Router

pipeline :browser do
  ...
  plug :fetch_live_flash
end

Examples

iex> put_flash(socket, :info, "It worked!")
iex> put_flash(socket, :error, "You can't access that page")

redirect(socket, arg2)

Annotates the socket for redirect to a destination path.

Note: LiveView redirects rely on instructing client to perform a window.location update on the provided redirect location. The whole page will be reloaded and all state will be discarded.

Options

  • :to - the path to redirect to. It must always be a local path
  • :external - an external path to redirect to

send_update(module, assigns)

Asynchronously updates a Phoenix.LiveComponent with new assigns.

The component that is updated must be stateful (the :id in the assigns must match the :id associated with the component) and the component must be mounted within the current LiveView.

When the component receives the update, the optional preload/1 callback is invoked, then the updated values are merged with the component's assigns and update/2 is called for the updated component(s).

While a component may always be updated from the parent by updating some parent assigns which will re-render the child, thus invoking update/2 on the child component, send_update/2 is useful for updating a component that entirely manages its own state, as well as messaging between components mounted in the same LiveView.

Note: send_update/2 cannot update a LiveComponent that is mounted in a different LiveView. To update a component in a different LiveView you must send a message to the LiveView process that the LiveComponent is mounted within (often via Phoenix.PubSub).

Examples

def handle_event("cancel-order", _, socket) do
  ...
  send_update(Cart, id: "cart", status: "cancelled")
  {:noreply, socket}
end

send_update_after(module, assigns, time_in_milliseconds)

Similar to send_update/2 but the update will be delayed according to the given time_in_milliseconds.

Examples

def handle_event("cancel-order", _, socket) do
  ...
  send_update_after(Cart, [id: "cart", status: "cancelled"], 3000)
  {:noreply, socket}
end

static_changed?(socket)

Returns true if the socket is connected and the tracked static assets have changed.

This function is useful to detect if the client is running on an outdated version of the marked static files. It works by comparing the static paths sent by the client with the one on the server.

Note: this functionality requires Phoenix v1.5.2 or later.

To use this functionality, the first step is to annotate which static files you want to be tracked by LiveView, with the phx-track-static. For example:

<link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>

Now, whenever LiveView connects to the server, it will send a copy src or href attributes of all tracked statics and compare those values with the latest entries computed by mix phx.digest in the server.

The tracked statics on the client will match the ones on the server the huge majority of times. However, if there is a new deployment, those values may differ. You can use this function to detect those cases and show a banner to the user, asking them to reload the page. To do so, first set the assign on mount:

def mount(params, session, socket) do
  {:ok, assign(socket, static_change: static_changed?(socket))}
end

And then in your views:

<%= if @static_changed? do %>
  <div id="reload-static">
    The app has been updated. Click here to <a href="#" onclick="window.location.reload()">reload</a>.
  </div>
<% end %>

If you prefer, you can also send a JavaScript script that immediately reloads the page.

Note: only set phx-track-static on your own assets. For example, do not set it in external JavaScript files:

<script defer phx-track-static type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

Because you don't actually serve the file above, LiveView will interpret the static above as missing, and this function will return true.

transport_pid(socket)

Returns the transport pid of the socket.

Raises ArgumentError if the socket is not connected.

Examples

iex> transport_pid(socket)
#PID<0.107.0>

update(socket, key, func)

Updates an existing key in the socket assigns.

The update function receives the current key's value and returns the updated value. Raises if the key does not exist.

Examples

iex> update(socket, :count, fn count -> count + 1 end)
iex> update(socket, :count, &(&1 + 1))

Callbacks

handle_call(msg, {}, socket)

Specs

handle_call(
  msg :: term(),
  {pid(), reference()},
  socket :: Phoenix.LiveView.Socket.t()
) ::
  {:noreply, Phoenix.LiveView.Socket.t()}
  | {:reply, term(), Phoenix.LiveView.Socket.t()}

handle_event(event, unsigned_params, socket)

Specs

handle_event(
  event :: binary(),
  unsigned_params(),
  socket :: Phoenix.LiveView.Socket.t()
) ::
  {:noreply, Phoenix.LiveView.Socket.t()}
  | {:reply, map(), Phoenix.LiveView.Socket.t()}

handle_info(msg, socket)

Specs

handle_info(msg :: term(), socket :: Phoenix.LiveView.Socket.t()) ::
  {:noreply, Phoenix.LiveView.Socket.t()}

handle_params(unsigned_params, uri, socket)

Specs

handle_params(
  unsigned_params(),
  uri :: String.t(),
  socket :: Phoenix.LiveView.Socket.t()
) :: {:noreply, Phoenix.LiveView.Socket.t()}

mount(arg1, session, socket)

Specs

mount(
  unsigned_params() | :not_mounted_at_router,
  session :: map(),
  socket :: Phoenix.LiveView.Socket.t()
) ::
  {:ok, Phoenix.LiveView.Socket.t()}
  | {:ok, Phoenix.LiveView.Socket.t(), keyword()}

The LiveView entry-point.

For each LiveView in the root of a template, mount/3 is invoked twice: once to do the initial page load and again to establish the live socket.

It expects three parameters:

  • params - a map of string keys which contain public information that can be set by the user. The map contains the query params as well as any router path parameter. If the LiveView was not mounted at the router, this argument is the atom :not_mounted_at_router
  • session - the connection session
  • socket - the LiveView socket

It must return either {:ok, socket} or {:ok, socket, options}, where options is one of:

  • :temporary_assigns - a keyword list of assigns that are temporary and must be reset to their value after every render

  • :layout - the optional layout to be used by the LiveView

render(assigns)

Specs

render(assigns :: Phoenix.LiveView.Socket.assigns()) ::
  Phoenix.LiveView.Rendered.t()

terminate(reason, socket)

Specs

terminate(reason, socket :: Phoenix.LiveView.Socket.t()) :: term()
when reason: :normal | :shutdown | {:shutdown, :left | :closed | term()}

© 2018 Chris McCord
Licensed under the MIT License.
https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html