mwmiller

mwmiller

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”.

Showing Posts 1 to 10

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!

mwmiller

mwmiller OP

Thank you for the comprehensive response. It’s nice to know that I am, at least, thinking clearly about how things should work.

I believed I had tried the suggested approaches, but I was likely mixing and matching things while hacking around. I’ll take this as a guide and give it another go.

If that doesn’t all pan out, I’ll return with an opened up repository and specific concerns.

Thanks again for your time and suggestions.

mwmiller

mwmiller OP

I forgot to mention that this was part of my issue. It was unclear to me how to trigger a command from an on change event. Using $change I ended up writing an action which queued the background command and set the :in_progess state. When that command returns it puts an action to the page which sets the resulting status (because I cannot directly put_state with the server.)

I imagine all of this indirection is part of my “breaking” update pipelines.

Some horrendous code hereabout:

<button $click="reload_model" title="request reload">↺</button>

  def action(:reload_model, _params, component) do
    put_command(component, :background_reload, %{})
    |> put_action(:refresh, model_reload: :in_progress)
  end

  def action(:refresh, params, component) do
    put_state(component, params)
  end

  def command(:background_reload, _params, server) do
    status = GenServer.call(:updater, :start_run)
    put_action(server, :refresh, model_reload: status)
  end

Also, FWIW, I have a bunch of {%if}s to pick out the way to display the button based on the model_reload status. I tried to abstract it into a case in init but it doesn’t seem to get called like I was expecting.

mwmiller

mwmiller OP

It was unclear to me how to trigger a command from an on change event.

Thinking now after learning how target: gets applied, I imagine it’s just using command: instead of action: therein. You can lead a horse to water.. :slight_smile:

bartblast

bartblast

Creator of Hologram

Right! :+1: You can streamline this by using the command directly in the template with the longhand syntax - no need for the intermediate action:

<button $click={command: :reload_model}" title="request reload">↺</button>

but I noticed that you’re probably also adding some loading indicator:

|> put_action(:refresh, model_reload: :in_progress)

this could also be done differently - you can change that state already in the “reload_module” action instead of creating a separate action for that.

Can you show me some code? What is happening?

mwmiller

mwmiller OP

Sadly, not what I actually had. I’ve ripped that out. I’ll write something similar here and hopefully not accidentally get it right.

prop :status, :atom, default: :eligible

def init(params, component, _server) do
   settings = case params.status do
    :eligible -> %{title: "reload data", text: "<that little refresh thing>"}
    :in_progress -> %{title: "in progress", text: "..."}
   end
   put_state(component, settings)
 end

def init do
 ~HOLO"""
<button title="{@title}">{@text}</button>
"""

From the “data hub page” this is set by something like “ModelReload status={@model_reload} /”… but init doesn’t seem to run. it complains about :title being missing from %{}. Which I mostly get: I didn’t add a prop for it and it won’t exist if init isn’t run. On the other hand being an empty map is also a bit surprising since I would have expected :status to make it in no matter how we arrived here.

If I leave off the init and write a bunch of independent {%if}s (working from `@status, one per atom) it seems to work fine, including getting the updates and applying changes.

BTW, I appreciate your time on this but I’m not really pulling my weight here. Feel free to pause your responses until I have a chance to do a more thorough review of my patterns and try to apply some of the stuff where you’ve alreayd provided guidance.

mwmiller

mwmiller OP

Just wanted to follow up before I head to bed.

Some combination of rubber-ducking and your helpful guidance got my head squared away somehow. I haven’t completed my conversion, but I’ve had enough clarity to make strong progress. I expect I will be able to complete the conversion to “page as hub” when I get back at it in the morning.

For the record, some of the key problems were:

  • half-baked conversions wherein I had left off a key piece of the switch from old to new style,
  • the MacOS file system watcher behaving poorly wherein I was working not diagnosing the proper running version, and, of course,
  • some lack of reading comprehension with respect to the hologram.page docs.

It all does, in fact, work like one would expect… if one works it like one ought.

I thank you again for your time and patience. I know it can be a drag when your correspondent can’t even properly articulate where his problems lie.

mwmiller

mwmiller OP

I’m about done prattling on about this, but I resolved this my using an additional property:

  prop :from_status, :map,
    default: %{
      eligible: %{text: "↺", title: "request reload"},
      in_progress: %{text: "…", title: "in progress"},
      done: %{text: "✓", title: "reload complete"},
      rate_limit: %{text: "⏲", title: "rate limited"}
    }

This provides some additional configurability which I will probably never use. Importantly, however, I can change the appearance and requirements inside the component alone while only needing the semantic :status as an externally provided property.

@bartblast I thank you again for your assistance in straightening out my thinking as well as for Hologram itself. I think I am “getting it” enough now to really appreciate the value it’s providing.

bartblast

bartblast

Creator of Hologram

I’m so glad you finally worked it out @mwmiller! :slight_smile:

Don’t hesitate to reach out for help anytime, questions like yours are invaluable to me because they help me understand exactly where new users trip up and where the documentation needs improvement (and sometimes reveal potential bugs too!).

I’m curious - what specific parts of the process tripped you up the most? Was it:

  • The conceptual understanding of the “page as hub” pattern?

  • The technical implementation details around init and component lifecycle?

  • The macOS file system watcher issues? (tell me more about this - it’s not clear to me whether you meant Hologram live reload)

  • Something in the documentation that was unclear or missing?

  • Any error messages that could be improved or made more helpful?

Understanding these pain points will help me prioritize which docs to improve first, where to add more examples or clarifications, and what new features or enhancements to add to Hologram itself.

mwmiller

mwmiller OP

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? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
csmr
Hi all, I’m new here and would like to hear peoples views on my Elixir/Phoenix based game-engine! :smiley: Mutonex gamedesign was finish...
New
Stef-Verniers
Hello I’ve been coding in Elixir for almost two years now because of my current job where we implemented Elixir in our application. But ...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
kip
In 2021 I started a new library called Tempo with the objective of modelling time as a set of intervals - not as instants. In 2022 I gave...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews