If you use Elixir/Phoenix and struggle with forms and complex UI, you are not alone. In this article I will show you how to solve these problems with LiveSvelte.
What Is LiveSvelte
LiveSvelte is a library that allows you to use Svelte components inside your Phoenix code not loosing any of the Phoenix jazz (SSR, state change, etc).
Alternatives?
LiveSvelte is not alone in this space there are alternatives to run React and Vue code.
LiveSvelte In Action
Basic LiveView component that has counter property in its state and increase_counter
event handler that increases the counter
:
defmodule LiveviewpreviewWeb.CaseLive.Index do
use LiveviewpreviewWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok, socket |> assign(counter: 0)}
end
@impl true
def handle_event("increase_counter", _params, socket),
do:
{:noreply,
socket |> assign(counter: socket.assigns.counter + 1)}
end
Layout for this component that renders counter
variable natively and another component — SvelteCounter
:
<div class="w-full">
<div class="flex flex-col w-full gap-2 items-center justify-center mb-4">
<div class="flex flex-col w-1/2 gap-2 justify-center text-center">
<p class="text-lg font-bold">Counter:</p>
<p class="font-bold"><%= @counter %></p>
<.button phx-click="increase_counter">
Increase On The Server
</.button>
</div>
</div>
<div class="w-full h-[2px] bg-gray-200 my-4" />
<LiveSvelte.svelte
name="SvelteCounter"
props={%{counter: @counter}}
/>
</div>
SvelteCounter
component looks like this:
<script lang="ts">
import { Live } from "live_svelte";
export let live: Live;
export let counter: number;
let local_counter: number = 0;
const increase_counter = () => {
live.pushEvent("increase_counter");
};
$: increase_local_counter = () => {
local_counter++;
};
</script>
<div class="flex flex-row w-full gap-2 justify-center">
<div
class="flex flex-col w-1/3 gap-2 justify-center align-center text-center"
>
<p class="text-lg font-bold">Svelte Counter:</p>
<p class="font-bold">{counter}</p>
<button
class="phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3 text-sm font-semibold leading-6 text-white active:text-white/80"
on:click={increase_counter}>Increase On The Server</button
>
</div>
<div
class="flex flex-col w-1/3 gap-2 justify-center align-center text-center"
>
<p class="text-lg font-bold">Svelte Local Counter:</p>
<p class="font-bold">{local_counter}</p>
<button
class="phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3 text-sm font-semibold leading-6 text-white active:text-white/80"
on:click={increase_local_counter}>Increase Local Counter</button
>
</div>
</div>
Let’s look at the code above in SvelteCounter component more closely. In the script tag we have several external variables:
-
live
— inserted in every component, holds LiveView callbacksexport type Live = { pushEvent(event: string, payload?: object, onReply?: (reply: any, ref: number) => void): number pushEventTo(phxTarget: any, event: string, payload?: object, onReply?: (reply: any, ref: number) => void): number handleEvent(event: string, callback: (payload: any) => void): Function removeHandleEvent(callbackRef: Function): void upload(name: string, files: any): void uploadTo(phxTarget: any, name: string, files: any): void }
-
counter
— LiveView state variable passed from LiveView layout ofLiveviewpreviewWeb.CaseLive.Index
Additionally, we have local_counter
variable that holds internal component state.
In the HTML section we display two counters:
-
Svelte Counter — counter variable passed from the LiveView component with corresponding button “Increase On The Server” that calls
increase_counter
function that useslive.pushEvent
to sendincrease_counter
event back to the LiveView component (LiveviewpreviewWeb.CaseLive.Index
) -
Svelte Counter Local —
local_counter
variable created within this Svelte component with corresponding button “Increase Local Counter” that callsincrease_local_counter
function that simply increases thelocal_counter
variable’s value
Demo
In the demo below you can see how the state of the LiveView component is correctly rendered in LiveView’ layout and in Svelte component, with both buttons working exactly the same keeping the local state variable untouched.