teamon

teamon

I’m reading through the example todo_trek app to try to understand how it all works.
One thing I’ve noticed is that in the validate event handler the edited todo is built from incoming params:

https://github.com/chrismccord/todo_trek/blob/main/lib/todo_trek_web/live/todo_list_component.ex#L123

While this works for the todo_trek app, it will fail when some other non-editable field is necessary to properly render the stream entry - let’s say for the sake of example that I’d like to render todo.inserted_at for every todo.

Since streams do not keep data in memory, in the validate handler I need to fetch todo from database to keep the inserted_at field rendered. This is not ideas, as the validate handler will be called multiple times, even when using phx-debounce.

One solution I can think of is to provide a short-lived cache using process dict with timer-based ttl.

What would be the preferred solution here?
Is there a way to avoid validate events at all?

Showing Posts 1 to 7

cmo

cmo

I don’t see why you would need to query the database on every change to get information that the client already has. You ensure the client sends it back by making it part of the form, no?

teamon

teamon OP

The inserted_at field is not part of the form, it’s just rendered as form.data.inserted_at.

On the first render it’s all good because form.data is a full Todo from database, but after validate event it has only id and list_id fields and rendering of inserted_at breaks

cmo

cmo

What is stopping you from making it part of the form? You want that data, you can get it by making it part of the form, you can keep it in memory or you can query for it.

With regards to your process dictionary cache idea, I’d probably use a proper cache so you’re not hand building a cache in every LiveView process. Kind of defeats the point of using streams in the first place doesn’t it?

KP123

KP123

Save them in your markup with phx-value-* properties. As outlined in the docs here you could add phx-value-inserted-at to each todo then on event send this values.

Using an in memory cache makes no sense here as you could just save the struct directly in your live view state without having to manage a new dependency.

teamon

teamon OP

Sending a single value might be ok, but imagine you need to also render Todo author avatar and you end up sending multiple values just to rebuild the Todo and User and Avatar schemas by hand. This quickly becames unmanagable and extremaly prone to errors. Not to mention that even with single inserted_at field it can be only sent as a binary so you need to convert it to proper DateTime which is just another layer of complexity. And if you render another field but forget to send it back you will only notice when you try to edit the Todo as the first render will work just fine.

I don’t agree the cache doesn’t make sense - with a long lists you can still benefit from not having to keep everything in memory but only to recently used items.

A proof of concept of a process-local cache could be something like this:

defmodule LiveCache do
  @ttl :timer.seconds(60)
  
  def fetch(key, ttl \\ @ttl, fun) do
    case Process.get(key) do
      nil ->
        value = fun.()
        put(key, value, ttl)
        value

      {value, timer_ref} ->
        Process.cancel_timer(timer_ref)
        put(key, value, ttl)
        value
    end
  end

  def put(key, value, ttl) do
    ref = Process.send_after(self(), {__MODULE__, :clear, key}, ttl)
    Process.put(key, {value, ref})
  end

  def clear(key), do: Process.delete(key)
end

## in LiveView process
def handle_info({LiveCache, :clear, key}, socket) do
  LiveCache.clear(key)
  {:noreply, socket}
end

## in (nested) LiveComponent
def handle_event("validate", %{"id" => id, "todo" => data}, socket) do
  todo = LiveCache.fetch({Todo, id}, fn -> Todos.get_todo!(id) end)
  {:noreply, stream_insert(socket, :todos, to_change_form(todo, data, :validate))}
end

While it’s possible to use socket.assigns as the cache storage it seems more complex to work with when using nested live components that have their own state. If the cache is to be shared between components it must be passed as attributes, potentially causing unnecessary rerendering.

A different way of solving this issue would be to have two zipped streams like this:

todos = Todos.list_todos()
forms = Enum.map(todos, &to_change_form(&1, %{}))

# ...

<%= for {todo, form} <- zip(@streams.todos, @streams.forms) do %>
  <.avatar user={todo.user}/>
  <.form for={form}> ... </.form>
<% end %>

and then allow updates to one stream only while keeping the other intact.

cmo

cmo

You have the same problem with forgetting to cache a value, no?

Is a cache per liveview better than a cache between all the liveviews and the database? You don’t think you’ll be caching the same thing in each liveview for each person on the page? And if you’re caching most of the data structure why bother with streams at all?

Are you sure that having a cache in the process dictionary that is designed to be accessed/edited in multiple places is easier to manage and reason about than passing it down explictly?

KP123

KP123

If you need the data in memory on the server don’t use streams, then you only need to send the ID of the entity you want to work on back to live view.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews