Does Hologram support two way data binding?

I have a state in Hologram lets say %{age: “40”}

I have an input like so

<input type="text" name="age" value={@age} />

I want to bind the value of input to the age.

I can do this

<input type="text" name="age" value={@age} $change="update_age" />

And then I can do

def action(:update_age, %{event: %{value: age}}, component) do
    put_state(component, age: age)
end

Is there another way to do this?
Does/will Hologram support two way data binding?

3 Likes

A post was split to a new topic: String.to_integer and Integer.parse! don’t work

There’s an important limitation to be aware of in v0.5.0. The issue you’re experiencing is that text-based input DOM elements are stateful and prioritize user-typed input over value attribute updates (this is a general DOM/HTML behavior, not specific to Hologram).

This is actually the same problem that Phoenix LiveView users encounter, as discussed in this Elixir Forum thread: HTML <input value doesn't reflect update in assign. When a user has already typed anything into an input field, you can’t change the input value through the value attribute programmatically - the browser maintains its own internal state.

Good news though! This limitation is being addressed in v0.6.0, which is already implemented on the dev branch. You can try it out by updating your mix.exs:

{:hologram, git: "https://github.com/bartblast/hologram.git", ref: "84867a2869fa4f58a01e3849eba3b7525773350d"}

With this version, your current code will essentially work like true two-way data binding (no code changes needed in your example). The framework will handle the DOM state management properly, so when you update the component state through your $change event handler, the input value will update correctly.

Eventually, I’m planning to add some sugar syntax for this similar to how Vue or Svelte handle it, but for now, the manual approach (like in your example) works well.

5 Likes

@sreyansjain I need to correct my previous advice about using the dev branch.

I shouldn’t have instructed you to use the head of the dev branch (branch: "dev") because there’s currently some incomplete work on CSRF protection that will probably prevent your app from sending commands properly. And generally you shouldn’t rely on the head of the dev branch either. The framework is actively being developed, so sometimes the dev branch can be in a transitional state.

Instead, use the last “green” commit from the dev branch:

{:hologram, git: "https://github.com/bartblast/hologram.git", ref: "84867a2869fa4f58a01e3849eba3b7525773350d"}

This commit has the two-way data binding improvements I mentioned, but without the breaking CSRF changes that are still in progress. Your original code example should work correctly with this specific commit.

Sorry for the confusion!

PS: original post amended…

3 Likes

The code in the OP is quite explicitly a one-way binding. Two-way binding is very bad and should be avoided. Probably one of React’s greatest contributions was killing it.

Unfortunately this problem is essentially impossible to fix with LiveView because it turns into a consistency problem. A controlled input in LiveView would have too much latency.

This is actually a pretty interesting advantage for Hologram because you could use exactly the same logic to validate/control the input on the client and server, which would be pretty cool if you get the API right.

1 Like

Fair point on the terminology - I made a mental shortcut and incorrectly called it “two-way binding” when what I actually implemented is controlled components where the input value always reflects the current state AND user input updates the state through explicit handlers - which is what the OP wants to achieve (I assume), just with proper unidirectional data flow (solving the DOM synchronization problem that LiveView has).

Could you recommend better terminology for this? I’m thinking along the lines of “controlled input components” or “reactive form controls,” but I want to make sure users understand what this achieves functionally. Since we’re in a functional language context where there’s no true “binding” per se, what would best communicate that inputs now properly reflect state changes? I used “two-way binding” as shorthand for what users typically want - inputs that stay synchronized with state - but I realize that term has specific technical implications I should have avoided. (I should probably update the thread title too.)

And yes, you’ve hit on exactly the key advantage here: because Hologram runs client-side and is close to the DOM, it can solve consistency problems that are fundamentally unsolvable in LiveView due to the server-client latency gap. The ability to run the same validation/control logic on both client and server is indeed a significant architectural benefit :slight_smile:

1 Like

One-way (unidirectional) data flow is good! Two-way binding is just literally the opposite of that. I can’t speak to why the OP brought it up, but the two-way binding approach (see e.g. OG Angular) was very bad and React’s one-way dataflow obsoleted it.

The code in the OP is indeed one-way, and it sounds like Hologram encourages the React approach, which is good.

They are generally called controlled components in React, but that refers to a specific pattern: letting React own the state instead of keeping state in the DOM. The reason this terminology exists is to promote that pattern, as in the past many thought the idea of putting everything in JS was ridiculous and it took time for people to realize that was a good idea (“rethinking best practices” indeed).

Nowadays React is the default many web devs are trained on so I’m not sure if you really need to explain this stuff or not. You’ll probably have to work with feedback from people learning Hologram down the road for stuff like this, one person (me) is not representative at all.

As the author of the framework you are thinking in implementation details. Most developers have no idea how React (or Hologram) works, they just know that when they set value={myValue} the thing on the page updates. That abstraction is good enough for them, there is no need to cause confusion by going any deeper.

1 Like

Fwiw, “two-way binding” for me is associated with svelte’s bind: which imo is nice and doesn’t present any issues particularly in the scenario discussed in this thread. If you use it to communicate up to a parent component, I could see how that could get hairy if you’re not careful.

These seem less clear imo

Agreed

1 Like

The code is indeed one way data binding.
I wanted to know is there another approach or a shorthand to sync the input value with the state or more specifically update the state automatically on input value changes.

Liveview cannot have 2 way bindings because of the round trip, but in case of hologram there is no round trip required for achieving this.
I hear JS frameworks these days are using signals and there is already a proposal for implementing the same in the browsers.

Updating an input value can cause the signal to change which for all practical purposes is as good as two way reactive data bindings.

I don’t have much experience with front end tech, I am mostly used to server driven development with liveview or alike. So my frontend knowledge might be a bit off.

1 Like

Form handling is such a fundamental feature that I think we need to nail down clear, consistent terminology for how Hologram approaches it. It’s one of the most common client-side use cases and probably what trips up LiveView users the most - as discussed in this BlueSky thread. I’m planning a dedicated “Forms” page for the website since good and consistent docs are crucial for good DX.

I totally agree that most developers won’t care about implementation details and just want value={@my_value} to work. That abstraction is perfect for them. However, some developers will want to compare Hologram with other frameworks, and having consistent naming for this pattern will be helpful in discussions and documentation.

Based on the feedback here, I’ll definitely avoid “two-way binding” or “bidirectional binding” since these don’t accurately describe what Hologram does and have historical baggage.

I’m considering these alternatives:

  • “Controlled Inputs” - familiar to React developers
  • “State-Synchronized Inputs” - descriptive for newcomers
  • “Input Synchronization” - focuses on the key benefit

(or “Form Elements” instead of “Inputs”)

The core concept is that Hologram maintains unidirectional data flow while solving the DOM synchronization problem that LiveView can’t address.

What do you think would be the clearest terminology? I want to get this right since it’ll be used throughout the documentation and forum discussions.

4 Likes

This is a topic full of footguns though. The value attribute of inputs in html is used to set the default value of that input, the value reverted to e.g. when you reset a form (<input type="reset">). The value of the input itself is the input.value property of the dom node (vs. input.getAttribute("value")). You can ignore that distinction – LV by my understanding does so – but it’ll mean you might break expectations for people who understand and use that distinction.

3 Likes

If you’re going to refer to the pattern I’d just call it a “controlled input” like React because that terminology is widely used and understood.

The point I was trying to make, though, is that pattern might well be so widely understood that many webdevs probably don’t actually recognize the contrast which it draws.

And this is just the thing. React JSX (and presumably Hologram templates) are not actually HTML, they are a tool which looks a lot like HTML and which causes similar HTML to appear on the page, but if you squint there are things like this which are totally different.

But if you were to ask the average React dev about how the value attribute works I’d bet they would reflexively describe the React behavior (which is far more intuitive anyway) rather than the HTML behavior, if they are aware of the HTML behavior at all.

For reference, here is how the React docs describe this behavior. Note that they draw no contrast with the HTML behavior (despite explicitly defining their own defaultValue attribute).

1 Like

That’s one path to take for sure. LV does the same. I’m using solid.js at work and they recently had a discussion on the design of v2, where they were looking for removing such special cases – which is the less brittle option. But my point was less in favor of one or another solution, but the fact that one needs to be aware of that stuff as a maintainer for an abstraction over html and where there’s deviation from the norm (usually those templates do not set properties) it needs to be well documented.

3 Likes

IMO, “controlled input” is unclear. I would go with something that is more on the descriptive side, maybe your 2nd idea.

You can always have a little callout if you want to cater to React devs, e.g. (if you’re coming from React, you may know this as “controlled input”).

3 Likes

The value and defaultValue thing is a small deviation, but of course React’s model is an enormous deviation from a normal HTML page. As is SolidJS. The reason these frameworks are so aggressively overwriting the input values is that they are specifically designed to transfer that control to the JS code (“controlled” component). This is essentially React’s raison d’etre, and likewise for all of its derivatives.

But the question is what the “norm” really is; a lot of people’s introduction to webdev was React, and the actual HTML behavior is the one that will be alien to them.

For the record, it seems like LiveView’s docs don’t mention this explicitly either.

SolidJS has no mention of inputs in their docs from what I saw, but this discussion suggests they don’t actually have this behavior, and many seem to be surprised and confused by the normal HTML behavior, which is kind-of my point.

Edit: I don’t know much about Vue but it seems like they have a special binding syntax for this which deviates slightly (:value="myValue"). There also seems to be some other input binding behavior which I don’t really understand. Maybe someone familiar can explain?

Either way they kind-of gloss over the HTML behavior too, though they are at least using a different syntax. I still like React’s approach more as it reinforces the idea that you are declaratively specifying the page state rather than imperatively updating it.

1 Like

I hear what you’re saying @LostKobrakai about the HTML behavior distinctions and potential footguns.

I’m leaning towards optimizing for the most intuitive developer experience as of 2025, which probably aligns with the React approach. I agree with @garrison’s observations - many developers today aren’t aware of the native HTML behavior and treat React’s behavior as the default one.

For example, most React developers don’t know that for the “change” event on text-based inputs, React actually uses the native “input” event under the hood. That’s actually the most intuitive approach in my opinion, and it’s what Hologram uses too.

I’m aware of the native “reset” behavior you mentioned, but I’ve consciously ignored it for the reasons I’m outlining here. While it would be technically possible to include this behavior, most people wouldn’t be aware of it and would still prefer the declarative/functional approach of managing everything through state changes rather than relying on implicit HTML form behaviors.

I think deviating from strict HTML semantics is worth it if it creates a better DX, but you’re absolutely right that it needs to be well documented. I want to be transparent about where Hologram differs from vanilla HTML behavior.

What other potential footguns do you think I should take into account and document? I’d rather identify these upfront and handle them thoughtfully than discover them later through user confusion.

2 Likes

Yeah, makes sense!

2 Likes

Can you please expand on this?

I assume you’re asking about the second part of that quote - “solving the DOM synchronization problem that LiveView can’t address.”

The fundamental issue is that the DOM is inherently stateful and LiveView’s architecture creates an unavoidable state consistency problem between DOM and server that requires ongoing reconciliation.

Here’s what happens in LiveView:

  1. User types “A” → DOM state becomes “A” (immediate)
  2. Event sent to server → server state becomes “A” (delayed)
  3. Server sends update back → client reconciles DOM with server state (delayed)

This creates all the fundamental distributed systems challenges: consistency problems, latency, ordering issues, partition tolerance, and failure modes (server restarts, connection drops, deployments, etc.). The DOM maintains its own internal state (what the user sees and interacts with), while the server maintains application state. Fast typing, network issues, or server processing delays create windows where these stateful systems are inconsistent, and the failure modes described above can result in complete state loss. During steps 2-3, you have two sources of truth that can diverge. LiveView’s client-side hooks are constantly working to reconcile these differences across the network boundary.

In Hologram:

  1. User types “A” → DOM state becomes “A” (immediate)
  2. Hologram updates component state → component state becomes “A” (immediate)
  3. DOM and component state remain synchronized (always)

There’s never a consistency gap because the DOM’s inherent statefulness is managed within the same execution context as the component state - one source of truth that gets updated atomically.

The key difference: LiveView must continuously resolve state inconsistencies between the inherently stateful DOM and server, while Hologram prevents state inconsistencies from occurring in the first place.

While LiveView’s hooks provide some mitigation, they’re fundamentally limited by the distributed architecture - solving a problem that Hologram’s architecture simply doesn’t have. You can’t have DOM/server synchronization issues when the DOM’s stateful nature and application state are managed in the same execution context.

That’s what I meant by “can’t address” - LiveView can’t eliminate the fundamental architectural challenge of coordinating two separate stateful systems, only manage it.

3 Likes

Thank you, so I assume the above and then this:

…means that your entire app in this case will be Elixir transpiled to Javascript, living in the browser in its entirety and thus avoiding to sync up with a server in the first place?

I am very not sure I am getting this right – likely not – but your analysis intrigued me, though I have near-zero context on Hologram. Still, if you are willing to expand on the “same execution context” and why does it avoid the sync / distribution problems exactly, it would be illuminating.

2 Likes