bunnylushington

bunnylushington

Seeking clarification on how commands and actions work together (wrt race conditions)

A question: I think i might be misunderstanding how commands and actions work together. In the code below, I would like to execute the command (changing the log level for the app) then reload the page ensuring the new log level is displayed. It appears – and I might be mistaken – that the command sometimes runs before the page refresh and sometimes after. It’s always run but sometimes I get the right current level and sometimes not. What is the idiomatic way of dealing with this in Hologram? (And apologies if I’m way off base here, I don’t do much web related programming…)

defmodule MyAppWeb.Hologram.Pages.Logging do
  @moduledoc false

  use Hologram.Page
  require Logger

  route "/logging"
  layout MyAppWeb.Hologram.Layouts.MainLayout,
         header: "Logging"

  def init(_params, component, _server) do
    component
    |> put_state(:current, Logger.level())
  end

  def action(:set_level, params, component) do
    component
    |> put_command(:set_level, params)
    |> put_page(MyAppWeb.Hologram.Pages.Logging)
  end

  def command(:set_level, params, server) do
    new_level = params.event.value
    Logger.configure(level:  String.to_atom(new_level))
    server
  end

end

Marked As Solved

bartblast

bartblast

Creator of Hologram

Here’s what’s happening and how to fix it:

The Issue

  • Commands are async - put_command/3 adds instruction to Component struct for Hologram runtime to execute after action finishes
  • Page navigations are also async - put_page/2 adds instruction to Component struct for Hologram runtime to execute after action finishes
  • When you do both from the same action, the runtime executes them in parallel - the command is sent to the server while the page is fetched from the server, causing nondeterministic behavior where either operation may finish first

Solution: Command Returns Action

Have your command return an action that navigates:

def action(:set_level, params, component) do
  # Only put the command instruction
  put_command(component, :set_level, params)
end

def command(:set_level, params, server) do
  new_level = params.event.value
  Logger.configure(level: String.to_atom(new_level))
  
  # Command returns action instruction
  put_action(server, :reload_page)
end

def action(:reload_page, _params, component) do
  put_page(component, MyAppWeb.Hologram.Pages.Logging)
end

This guarantees: Action → Command (Logger.configure) → Action (navigation)

Future Feature

put_page/2 and put_page/3 directly in commands is planned. This would allow adding the page navigation instruction directly from the command to the Server struct. This is already in the backlog and I’ll increase the priority.

This would solve these synchronization issues by allowing something like this - with no additional client-server roundtrip, the page would be rendered within the same server request just after the command:

def action(:set_level, params, component) do
  put_command(component, :set_level, params)
end

def command(:set_level, params, server) do
  new_level = params.event.value
  Logger.configure(level: String.to_atom(new_level))
  
  # Future feature - put_page directly in command (not yet implemented)
  put_page(server, MyAppWeb.Hologram.Pages.Logging)
end

or even shorter by triggering the command directly through the template without the action boilerplate:

~HOLO"""
<button $click={command: :set_level, params: %{level: :warning}}>Set level</button>
"""

Also Liked

bunnylushington

bunnylushington

Hey, thanks tons. This solves my problem (and provides better insight into how to work with Hologram generally). Appreciate you taking the time to respond.

Where Next?

Popular in Questions Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
logicmason
Hi there, I'm working through my first release with elixir/phoenix. I've built a release with distillery and found that it crashes when I...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
New

Other popular topics Top

lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New

We're in Beta

About us Mission Statement