Closed gevera closed 1 year ago
You need to save the state in LiveView. A way you can do this is for example use ETS: https://elixir-lang.org/getting-started/mix-otp/ets.html
The state is not being saved server side and resets on every page load. SSR in Svelte just makes sure on initial page load you have some rendered content instead of a blank page.
I'll also add your example to the example_project folder as it's quite nice!
chat_page.ex
defmodule LightBulbWeb.ChatPage do
use LightBulbWeb, :live_view
require Logger
def mount(_params, _session, socket) do
:ets.new(:messages_table, [:set, :protected, :named_table])
{:ok, assign(socket, messages: get_messages())}
end
def render(assigns) do
~H"""
<div class="max-w-screen-xl mx-auto p-4 flex flex-col gap-4">
<h1 class="text-center text-2xl font-light my-4">Messages</h1>
<LiveSvelte.render name="ChatInput" />
<LiveSvelte.render name="ChatList" props={%{messages: @messages}} />
</div>
"""
end
def handle_event("add_message", %{"message" => message}, socket) do
id = System.unique_integer([:positive])
:ets.insert(:messages_table, {id, message})
messages = get_messages()
{:noreply, assign(socket, %{messages: messages})}
end
def handle_event("remove_message", %{"id" => id}, socket) do
:ets.delete(:messages_table, id)
messages = get_messages()
{:noreply, assign(socket, %{messages: messages})}
end
defp get_messages() do
message_list = :ets.tab2list(:messages_table)
Logger.info(message_list)
message_list |> Enum.map(fn {id, text} -> %{id: id, text: text} end)
end
end
ChatList.svelte
<script>
import { fly } from "svelte/transition";
import { flip } from "svelte/animate";
export let messages = [];
export let pushEvent;
const removeItem = (id) => {
pushEvent("remove_message", { id: id }, () => {});
};
</script>
<div class="my-4 p-2">
{#if messages.length}
<ul class="list-inside list-disc p-4 flex flex-col gap-2">
{#each messages as message (message.id)}
<li
transition:fly
animate:flip
class="italic flex items-center justify-between w-full border rounded-md p-2"
>
<div>
{message.text}
</div>
<button
on:click={() => removeItem(message.id)}
class="flex items-center justify-center text-xl font-bold w-10 h-10 p-2 rounded bg-slate-100 hover:bg-brand active:bg-brand shadow-md hover:shadow-xl"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
</li>
{/each}
</ul>
{:else}
<h5 class="text-lg">No messages yet</h5>
{/if}
</div>
ChatInput.svelte
<script>
export let pushEvent;
let message = "";
const sendMessage = () => {
pushEvent("add_message", { message: message }, () => {});
message = "";
};
</script>
<form
on:submit|preventDefault={sendMessage}
class="flex flex-col sm:flex-row justify-between sm:items-center gap-4"
>
<input
type="text"
name="message"
required
bind:value={message}
class="flex-grow ring-brand focus:outline-brand focus:ring-0 border-none bg-slate-200 font-mono"
placeholder="Type in your message..."
/>
<button
type="submit"
class="flex-shrink bg-brand px-6 py-2 rounded-full uppercase text-white font-semibold hover:bg-orange-500 hover:shadow-xl"
>Send</button
>
</form>
Still not getting SSR even with ETS storage as ou have suggested =/
As a sidenote ETS was not working for me, but also has nothing to do with SSR, I think to make it work you need to do the following:
chat_page.ex
defmodule LightBulbWeb.ChatPage do
use LightBulbWeb, :live_view
require Logger
@table :messages_table
def mount(_params, _session, socket) do
{:ok, assign(socket, messages: get_messages())}
end
def render(assigns) do
~H"""
<div class="max-w-screen-xl mx-auto p-4 flex flex-col gap-4">
<h1 class="text-center text-2xl font-light my-4">Messages</h1>
<LiveSvelte.render name="ChatInput" />
<LiveSvelte.render name="ChatList" props={%{messages: @messages}} />
</div>
"""
end
def handle_event("add_message", %{"message" => message}, socket) do
id = System.unique_integer([:positive])
:ets.insert(@table, {id, message})
messages = get_messages()
{:noreply, assign(socket, %{messages: messages})}
end
def handle_event("remove_message", %{"id" => id}, socket) do
:ets.delete(@table, id)
messages = get_messages()
{:noreply, assign(socket, %{messages: messages})}
end
defp get_messages() do
message_list = :ets.tab2list(@table)
Logger.info(message_list)
message_list |> Enum.map(fn {id, text} -> %{id: id, text: text} end)
end
end
application.ex
def start(_type, _args) do
children = [
{NodeJS.Supervisor, [path: Application.app_dir(:example, "/priv/static/assets"), pool_size: 4]},
# Start the Telemetry supervisor
ExampleWeb.Telemetry,
# Start the Ecto repository
Example.Repo,
# Start the PubSub system
{Phoenix.PubSub, name: Example.PubSub},
# Start Finch
{Finch, name: Example.Finch},
# Start the Endpoint (http/https)
ExampleWeb.Endpoint
# Start a worker by calling: Example.Worker.start_link(arg)
# {Example.Worker, arg}
]
# Start the ETS table
:ets.new(:messages_table, [:set, :public, :named_table])
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Example.Supervisor]
Supervisor.start_link(children, opts)
end
Fixed, was a configuration issue
I have a question regarding server side rendering
LightStatusBar.svelte
LightControllers.svelte
live_page.ex
Even thought I trigger Pheonix function up/down to increase or decrease the brightness, on SSR I do get the initial state of 10%
What am I missing? Do I need to change somehow the handle_event functions or is it on the Svelte side?
Thank You