# Mastering Reconnects
---
## Slide 2
---
<.live_view_works_here yes={true} />
Giovanni Francischelli
- Brazilian ๐ง๐ท
- Cinema ๐ฅ
- Hiking โฐ๏ธ
- Swimming ๐๏ธ

sequenceDiagram Server->>Server: mount(), handle_params(), render() Server->>Browser: HTML v1 Browser->>Browser: Changes: HTML v2 Browser--xServer: Disconnect ๐ฅ Browser-->>Server: pls reconnect ๐ Server->>Server: mount(), handle_params(), render() Server->>Browser: HTML v1 Browser->Browser: Reset ๐ญ
Reconnect-fragility
def mount(_params, _session, socket) do
{:ok, assign(socket, :active_slide, 0)}
end
def render(assigns) do
~H"""
<.slide
:for={{slide, index} <- Enum.with_index(@slides)}
:if={index == @active_slide}
data={slide}
/>
"""
end
Use handle_params/3
to "re-mount" correctly
def handle_params(%{"index" => index}, _uri, socket) do
{:ok, assign(socket, :active_slide, String.to_integer(index))}
end
sequenceDiagram Server->>Browser: HTML v1 Browser->>Browser: Changes: HTML v2 Browser--xServer: Disconnect ๐ฅ Browser-->>Server: pls reconnect ๐ Server->Server: handle_params(slide=10) Server->>Browser: HTML v2 Browser->Browser: PROFIT ๐ค
Key Takeaways
- Fix UI resets by rendering the same HTML the browser has
- Use the DOM as a client data store
- Prefer CSS for conditional styling over assigns spaghetti
-
Use
connect_params
to sync client state with server during reconnects - Prefer "I haven't figured out" mindset over "It's a LiveView limitation"
Fragile ๐ญ
- I'm Still Here
- Chungking Express
- The Straight Story
- Indigo Blue
- Rattlesnake
- In the Garage
# Example Usage
<.live_component id="example" module={Tab}>
<:tab name="Movies">
<.movie_list />
</:tab>
<:tab name="Songs">
<.song_list />
</:tab>
</.live_component>
# TabLiveComponent.ex
def render(assigns) do
~H"""
<button :for={tab <- @tab}
class={tab.name == @active && "active"}
phx-click="activate"
phx-value-name={tab.name}>
{tab.name}
</button>
<div :for={tab <- @tab}
:if={panel.name == @active}>
{render_slot(tab)}
</div>
"""
end
def update(assigns, socket) do
{:ok, assign_new(socket, :active, fn -> assigns.tab[0].name end)}
end
def handle_event("activate", params, socket) do
{:noreply, assign(socket, active: params["name"])}
end
LiveComponent tabs recap
- Resets
assigns
to initial state during reconnects - Server roundtrips for pure UI logic adds unnecessary latency
assigns
based conditional styling
Resilient ๐ช
- I'm Still Here
- Chungking Express
- The Straight Story
- Indigo Blue
- Rattlesnake
- In the Garage
# Example Usage
<.tabs id="example">
<:tab name="Movies">
<.movie_list />
</:tab>
<:tab name="Songs">
<.song_list />
</:tab>
</.tabs>
def tabs(assigns) do
assigns = assign_new(assigns, :initial_tab, fn -> assigns.tabs[0].name end)
~H"""
<div id={@id} class="tabs" data-reset={reset()}>
<button
:for={{tab, index} <- Enum.with_index(@tab)}
phx-click={change_tab(id, index)}
data-active={tab.name == @initial_tab}
class="data-active:bg-purple"
>
{tab.name}
</button>
<div
:for={tab <- @tab}
data-active={tab.name == @initial_tab}
class="hidden data-active:block"
>
{render_slot(tab)}
</div>
</div>
"""
end
defp reset, do: JS.remove_attribute("data-active", to: {:inner, "[data-active]"})
defp change_tab(id, index) do
JS.exec("data-reset", to: {:closest, ".tabs"})
|> JS.set_attribute({"data-active", true}, to: "##{id} :nth-of-type(#{index + 1})")
end
Function component tabs
- Pure UI interactions via client
- Client-side state with HTML data attributes
- CSS-only conditional styling
Conditional styling via data-*
attributes
.panel {
display: hidden;
&[data-active] {
display: block;
}
}
More examples: styling the last element
When there's an active code block, dim the inactive
.slide:has(code[data-active]) {
code:not([data-active]) {
opacity: 50%;
}
}
Key Takeaways
- Fix UI resets by rendering the same HTML the browser has
- Use the DOM as a client data store
- Prefer CSS for conditional styling over assigns spaghetti
-
Use
connect_params
to sync client state with server during reconnects - Prefer "I haven't figured out" mindset over "It's a LiveView limitation"
Key Takeaways
- Fix UI resets by rendering the same HTML the browser has
- Use the DOM as a client data store
- Prefer CSS for conditional styling over assigns spaghetti
-
Use
connect_params
to sync client state with server during reconnects - Prefer "I haven't figured out" mindset over "It's a LiveView limitation"
What if we could something like this?
# PSEUDO CODE
def mount(_params, _sesion, socket) do
step =
if reconnecting?(socket) do
grab_step_from_browser_html(socket)
end
{:ok, assign(socket, :step, step || 0)}
end
Enter liveSocket
liveSocket.connect("/live", Socket, {
params: {_csrf_token: csrf_token }
}
)
Enter liveSocket
liveSocket.connect("/live", Socket, {
params: (view) => {
return {_csrf_token: csrf_token }
}
}
)
Sending arbitrary params during reconnects
liveSocket.connect("/live", Socket, {
params: (view) => {
return {
_csrf_token: csrf_token,
step: view.querySelector("[data-step]").dataset.step
}
}});
<div data-step={@step}>
Reading the connect_params
def mount(_params, _session, socket) do
connect_params = get_connect_params(socket)
step =
if connect_params["_mounts"] > 0 do
connect_params["step"]
end
{:ok, assign(socket, :step, step || 0 )}
end
A
sequenceDiagram Browser--xServer: Disconnect ๐ฅ Browser->>Browser: Recover data from DOM Browser-->>Server: pls reconnect ๐ (DATA) Server->Server: Read connect_params Server->>Browser: Correct HTML Browser->Browser: PROFIT ๐ค
Key Takeaways
- Fix UI resets by rendering the same HTML the browser has
- Use the DOM as a client data store
- Prefer CSS for conditional styling over assigns spaghetti
-
Use
connect_params
to sync client state with server during reconnects - Prefer "I haven't figured out" mindset over "It's a LiveView limitation"
Key Takeaways
- Fix UI resets by rendering the same HTML the browser has
- Use the DOM as a client data store
- Prefer CSS for conditional styling over assigns spaghetti
-
Use
connect_params
to sync client state with server during reconnects - Prefer "I haven't figured out" mindset over "It's a LiveView limitation"