Sustaining form state through event handling

Using LiveView, I have a form where I want some dynamic behavior based on the user’s interaction with the view. Let’s say I have a button or some trigger where my LiveView needs to handle an event and write to assigns, but this trigger is not at the form level.

How do I retain the ability to write to the form (via the assigns) while managing the interactive event? The LiveView event handler only carries the form data if the event is spawned from the Form itself. Thus, I cannot always update the assigns map with the user’s entered form values, and in my return of the socket it overwrites the user’s input.

Is there an idiomatic answer to this? I think there’s a way to do this if I compartmentalize and then add an assigns value which blocks form refresh from the database, but that sounds much more complicated than something which just leaves user text box entries the same.

Conversely, is there a way to read the input control values from the DOM to get the form value as an input into these surrounding events?

You can take a look at One-to-Many LiveView Form | Benjamin Milde. It uses additional buttons to alter a form.

1 Like

So, you’re basically fighting with LV :slight_smile:

I’ve been up that alley a few times too. You describe the issue quite clearly: the form state is overwritten if you do things outside the form, or if you don’t take into account the latest params, which only come with the handle_event callback. This is actually not a problem, most of the time, if you use phx-change on the form, because the form state will have sync’ed to the LV before you handle anything else. But if you forgo phx-change, or do other funky stuff, you’ll end up changing the state server-side, which doesn’t reflect the latest state on the client, and you might overwrite pending changes. But again, if you go the LV way of things (using phx-change), this isn’t a problem. If you change the assigns in this case, nothing is overwritten.

I don’t think you want to go the route of blocking the form from getting the latest assigns. I don’t think I’ve seen people do this. If you want to ignore changes, you’d use phx-update="ignore" on an element. But I don’t think that helps here.

So I’m not really sure if you actually have a problem right now, If you lean into the LV way of things (using phx-change). Maybe you should elaborate more, to give more context.

The only thing I can add to this, is that there is a little known gem in the way html forms work: you can set the form attribute on any input element even if it’s outside the form tag. It’s explained here. This might be a solution if you want to have a button somewhere outside your form, and still want to associate it with the form, and set things like phx-click on it.

I’m not so sure this is “fighting LV”. This is working with LV as it’s intended – clicks adjust assigns and the template rerendered with the new assigns updates the frontend. Any client side state not reflected by assigns is removed/replaced. Not using phx-change is imo much more in the realm of “fighting LV”, because assigns won’t be updated for form changes, but the button clicks are somehow meant to adjust the form from the server side. Or stated differently: You inform the server of only half of the changes to apply to the form state, but still expect it to work.

2 Likes

Hi - thanks for the response.

The basis of the problem is that I have a server event which can effect the state of the submit button - basically I want to be able to have another process send a message to enable or disable the submit button based on other conditions.

I’m not trying to fight Phoenix Change - maybe the solution is just to update server state continuously and then the assigns map is never out of sync with the form state and thus always be “correct.” When handling the external event, I will know that the changed state is independent of user entry because every keystroke is calling the phx-change callback.

Are you concerned that the form data will be overwritten by the server when you update that one assign for disabling the button? If you’re not changing the form data on the server then no update should be sent to the client for it. Edit: you do need to maintain the form state on the server though.

That is the core concern. If I’m not implementing phx-change rewriting the assigns map every time the form updates, the socket’s previous assign’s map containing stale data is passed into the event handler where the state effecting the button state is assigned.

I was assuming that there could be a way that I could either pass the socket back without overwriting the user’s form input, or poll the form for the input in the method so that I could update the assigns map when returning the socket.

I think phx-debounce="blur" on the inputs should have the same effect with less data back and forth.

I think that works in a lot of cases, but it will create some erratic behavior (I think) if the external event fires mid-edit by the user

LiveView won’t wipe an input that has focus.

Oh, that’s great to know!

In this case you won’t have any problem (you’re not fighting LV, shouldn’t have said that).

It’s not clear if the condition to enable/disable the button also takes into account any of the form state. But even if that’s the case, the end state will be ok when the focussed input is blurred again (the changes of input texts don’t sync on every keystroke, but on blur). If the enabled/disabled state is only reflecting something server-side, there will be no conflict at all.

And like @cmo mentioned, focussed input elements won’t ever be overwritten (see this paragraph for more info).

I’d just try it out. Should be fine.