mwmiller

mwmiller

Architecting a Page for Updates

I am building my first Hologram application. I have it working to do what I want, but I am pretty unhappy with my architecture as it stands. I am passing (what seems to me to be) too many actions and commands around to synchronize state between components.

On the page there are 3 major blocks, which I model as components.

The first is a simple data table which lists some items computed by the backend.
The second is a settings table which updates the user-supplied parameters for the contents of the table.
The third is a simple button to attempt to load new data from outside sources which are also used in the computation.

The idea is that “on change” ($change?) the settings table will inform the page that the settings have changed. Because the user input is sanitized (and perhaps mutated if it’s wonky), I want to push those changes back to the form. I am currently doing this with some nearby labeling. It would be cool if it actually put the “server-decided” values into the input form itself. Regardless, I also need to inform the data table that its contents have change and force a re-render.

The button is slightly more convoluted. I need to inform the component itself that we recognize that the button is pushed and are doing the magic in the background. When this completes, I need to receive another update which says whether it was successful. If it was successful, the data table needs to recompute and render itself.

I feel like there should be a way to use the containing page as a “data hub” and allow it to propagate the changes to its child components. I’ve not had the insight make that happen, as yet. I’ve tried using sessions, context and state. None of these have allowed me to reliably fire-and-forget the changing conditions and have the contained components update when their data changes.

Should I be using URL parameters for the settings bit? There are an awful lot of them and they would detract from the human-comprehensibility.

Any suggestions appreciated up to and including: “you’re thinking about this all wrong”.

Most Liked

bartblast

bartblast

Creator of Hologram

Hi Matt! You’re right that there are cleaner patterns available than passing many actions/commands around.

The “Data Hub” Pattern You Want

Hologram absolutely supports using the page as a “data hub”! Pages are essentially special components that can act as coordinators for all their child components. You have two main approaches for sharing data down to child components:

1. Page State + Manual Props
Store data in page state and explicitly pass it as props to child components. This gives you explicit control over what each component receives and is great when you want to be selective about data flow.

2. Context for Avoiding Prop Drilling
Use put_context/3 when you have deeply nested components or many components that need the same data (like your settings). Components declare prop :my_prop, :type, from_context: :my_key and automatically receive updates. This is perfect for avoiding prop drilling in your settings case where multiple components need access.

Other Key Mechanisms

Targeted Actions: Use the target parameter to have components trigger actions on the page. The page can then update its state or context, which automatically flows down to child components - you shouldn’t need to target individual components if you’re properly using the data hub pattern with props or context.

Commands for Server Work: For your button’s async external data loading, use put_command/2, then in the command callback use put_action/4 with target: "page" to notify when complete.

For Your Use Case

The page acts as coordinator - validates data, stores in state, and either passes props manually or uses context (for settings since multiple components need them). Data table gets settings and automatically refreshes. Button manages loading state, uses commands for server work.

This gives you centralized coordination with automatic re-renders when data changes, eliminating the need to manually sync state between components.

Would love to see some code snippets of your current approach - it would help me give more specific advice about which patterns would work best for your exact architecture and understand any specific challenges you’re facing.

The framework definitely supports the data hub pattern you’re looking for!

bartblast

bartblast

Creator of Hologram

That confusion you experienced is actually a perfect illustration of Hologram working exactly as intended - though I totally understand why it was disorienting! :sweat_smile:

The whole idea behind Hologram is that it should be completely seamless. You write Elixir everywhere, and the framework handles whether that code runs on the server (as native Elixir/BEAM) or gets transpiled to run on the client (as JavaScript). From your perspective as a developer, it should just be “Elixir code” regardless of where it executes.

So when you used Integer.count_digits/1 in what turned out to be client-side code, Hologram transpiled that to JavaScript and ran it in the browser - which is why you saw the error in the browser console instead of your server logs.

The stack trace situation you encountered is an important feature that I haven’t started working on yet, but it’s definitely on the roadmap. Eventually, you’ll get proper Elixir stack traces that point to your actual source files and line numbers, even for client-side errors. Right now that Elixir source mapping isn’t implemented yet, so you’re seeing the transpiled JavaScript stack traces. You can see this on the official Roadmap - “Client Error Stacktraces - Ensure error messages and stacktraces on the client match those on the server for easier debugging”.

The good news is that the transpiled code is very readable since it’s a one-to-one mapping from your Elixir code - but I know it’s still not ideal for debugging.

Your mental model of “actions probably run on client, commands definitely run on server” is actually pretty solid as a working heuristic! But the real beauty of Hologram is that you shouldn’t need to think about it at all - it should just work seamlessly no matter where the code runs.

I know this kind of architecture can be a little disorienting at first, but once you get the hang of how it works and what’s executed where, it becomes much easier and you really don’t want to go back to the traditional approach of juggling different languages and contexts.

Thanks for surfacing this!

mwmiller

mwmiller

I had this pretty well in my head. This is/was a conversion of a deployed LiveView app.
It worked well-enough but I had some ugly JS hooks to do the reactive update things.
I’m much more comfortable writing Elixir than JS, so Hologram seemed like a good way to limit the exposure of my ignorance.

I have written from React stuff for work. This feels conceptually similar, but I am far from an expert therein, so it was perhap more of a hinderance than a help.

This tripped me up a lot. Part of the “problem” with writing Elixir everywhere is that I assume that it is all running in a BEAM server context. I forget that some of it is being packaged up to run on client machines in a JS engine context. I have sort of settled on a conception that “actions will probably run on the client” and “commands will definitely run on the server.”

This is partially the live reload. It will sometimes have 404s for newly digested runtime- or page- files. (I saw there is a GitHub issue about this. I am still hoping to provide more reproduction steps thereon.) It seems like these 404s are where I felt like my changes had broken the data pipeline when, in fact, not much of anything was happening on the client.

As I mentioned opaquely before, it took me a long time to find how the DOM element events are fired. I looked on the Events page (which was correct) but somehow gave up before I hit the various syntaxes.

It would be crazy to switch up the organization because I refused to read fully. However, it would be extremely helpful to people like me if the left-hand navigation for the focused page had a list of sections (with #-links would be even better!) I also found the “contrast” between the headers and the text sections to be too low. I (probably) scanned down a bit and felt overwhelmed by the “wall of text” which seemed to be focused on different stuff than I expected.

The template rendering errors are exceptionally long stack traces which have seemingly no connection to the code as written. I’m really good at generating them, so let’s make one now!

[error] ** (KeyError) key :setting not found in: %{
  settings: [
    bankroll: 10000,
    max_count: 16,
    min_pct: 2.5,
    odds_style: :us,
    odds_amount: -105,
    model: "follow"
  ]
}
    (stonehands 0.2.0) app/wagersettings.ex:9: anonymous fn/1 in WagerSettings.template/0
    (hologram 0.5.0) lib/hologram/template/renderer.ex:416: Hologram.Template.Renderer.render_template/5
    (hologram 0.5.0) lib/hologram/template/renderer.ex:135: anonymous fn/3 in Hologram.Template.Renderer.render_dom/3
    (elixir 1.18.4) lib/enum.ex:2546: Enum."-reduce/3-lists^foldl/2-0-"/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:76: Hologram.Template.Renderer.render_dom/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:135: anonymous fn/3 in Hologram.Template.Renderer.render_dom/3
    (elixir 1.18.4) lib/enum.ex:2546: Enum."-reduce/3-lists^foldl/2-0-"/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:76: Hologram.Template.Renderer.render_dom/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:135: anonymous fn/3 in Hologram.Template.Renderer.render_dom/3
    (elixir 1.18.4) lib/enum.ex:2546: Enum."-reduce/3-lists^foldl/2-0-"/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:135: anonymous fn/3 in Hologram.Template.Renderer.render_dom/3
    (elixir 1.18.4) lib/enum.ex:2546: Enum."-reduce/3-lists^foldl/2-0-"/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:76: Hologram.Template.Renderer.render_dom/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:135: anonymous fn/3 in Hologram.Template.Renderer.render_dom/3
    (elixir 1.18.4) lib/enum.ex:2546: Enum."-reduce/3-lists^foldl/2-0-"/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:76: Hologram.Template.Renderer.render_dom/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:135: anonymous fn/3 in Hologram.Template.Renderer.render_dom/3
    (elixir 1.18.4) lib/enum.ex:2546: Enum."-reduce/3-lists^foldl/2-0-"/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:76: Hologram.Template.Renderer.render_dom/3
    (hologram 0.5.0) lib/hologram/template/renderer.ex:135: anonymous fn/3 in Hologram.Template.Renderer.render_dom/3

Ok, that first bit seems reasonable. I’ve apparently made a typo! Surely this giant traceback will help me find it! The first line seems helpful, except that there is no such typo on line 9. Maybe there’s more down here. Nope, none of that was written by me. Oh, I can just count 9 lines into the ~HOLO sigil which is apparently the anonymous fn in question!

None of the above should be taken as a harsh critique. I have enjoyed the overall experience. It just really exposes that I am much better at writing mathematically and algorithmically interesting backend code than building UIs.

Where Next?

Popular in Questions Top

chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
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
dotdotdotPaul
Okay, I’m having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I’m sure I’...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
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
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
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
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
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement