JohnnyCurran

JohnnyCurran

The Top 3 LiveView Form Mistakes (And How to Fix Them)

I’ve been writing LiveView since 2020. In that time, I’ve seen the same three form mistakes at multiple companies. Here’s what they are and how to fix them.

1. Slow, laggy forms with scattered logic because form state gets stored in socket assigns and server round-trips get used for dynamic UI (conditional inputs, toggles), instead of keeping that state in hidden form inputs where it belongs.

2. Brittle system where UI and database can’t evolve independently because database schemas get used directly for forms, coupling persistence logic to presentation.

3. Users stuck with valid data but can’t submit because changesets get manually manipulated with Map.put or Map.merge instead of Ecto.Changeset functions, leaving stale errors behind.

The common thread: don’t fight the framework. Keep form state on the client, create embedded schemas for your forms, and use Ecto.Changeset functions to modify changesets.

Most Liked

kevinschweikert

kevinschweikert

In Mistake #2, where you do:

 {:error, changeset} ->
      {:noreply, assign(socket, :form, to_form(changeset))}

How would you map the errors back to the form schema, when the fields from the DB are different?

tfwright

tfwright

I don’t have much professional experience with LiveView, just hobby projects. But RE 1, aren’t things a bit more complex than you suggest? You say “don’t fight the framework,” but naively it would seem using JS at all is fighting a framework the express intention of which is presumably to manage state on the server. Your example is a good one for your argument, seems like pure FE state that is relatively easy to handle (with latest JS integration). But in reality isn’t most state more ambiguous?

Take a table with rows that are selectable. On first glance this seems like something that should be handled on the FE. Certainly requiring a server trip to toggle selection creates lag, and arguably breaks a strong user expectation that checkboxes are highly responsive. But what happens when some BE action needs to know which rows are selected? Or even needs to rerender part of the view (e.g. to display some metadata about selected rows)? Obviously, you can solve this with more JS, but now all the defects you mentioned with a BE implementation apply: the logic is more spread out, more place for bugs, and arguably JS bugs are a lot more difficult to test and QA against (at least, that’s why we’re using LV in the first place, no?). Alternatively, one could try to use different tooling to alleviate issues with lag, like debouncing, optimistic UI updates, etc.

To be clear, I don’t think your advice is necessarily bad, in the end I think it is simply one of the challenges of development with LV to find the right balance here. But it is a balance, and a delicate one. The more JS features get added to a LV project the lower the ROI it seems. At a certain point, if you want to add a lot of these features, DX is going to go downhill in comparison with React.

JohnnyCurran

JohnnyCurran

Good question.

In that specific case branch, nothing needs to be done, because that changeset is the form changeset.

If there were an error in Accounts.register_user and you got a changeset back, you’d do something like (psuedo-ish code):

changeset = FormModule.changeset(%FormModule{}, form_params)
changeset
|> Ecto.Changeset.apply_action(:insert)
|> case do
  {:ok, form_params} ->
    form_params
    |> Map.from_struct()
    |> Accounts.register_user()
    |> case do
      {:error, user_changeset} ->
         # Figure out which user changeset field had an error
         # Place error in changeset, validate, re-assign
         socket =
           changeset
           |> Ecto.Changeset.put_error(:field, "There was an error saving to the database!")
           |> Map.put(:action, :validate)
           |> to_form()
           |> then(&assign(socket, :form, &1))
         
        # ... rest of handler

Is how I’ve done it before

Where Next?

Popular in Blog Posts Top

lawik
One of the Erlang ecosystem’s spiciest nerd snipes are hot code updates. Because it can do it. In ways that almost no other runtime can.
New
Paradox
Testing is an important part of any modern piece of software. But writing tests can quickly become an exercise in frustration, with tons ...
New
JKWA
I didn’t have room for this in my book, Advanced Functional Programming with Elixir , but I still thought it was worth sharing on my blog.
New
brainlid
Elixir has a built-in Zip library that comes with OTP. This post explores how to use the zip module and asks the important question: “Is ...
New
danschultzer
How to set up the Content Security Policy header with Phoenix LiveView and support inline styles and scripts with CSP nonces.
New
rms.mrcs
https://medium.com/the-realreal-product-tech-blog/monitoring-hackney-connection-pools-with-telemetry-3aaeafa8eeb8 Hi, everyone! In this ...
New
brainlid
In a 2 day spike, I created my own Elixir-based AI Personal Fitness Trainer! The surprising part for me was how useful and helpful I foun...
New
brainlid
I love LiveView. Navigating between views is so fast! This quick tip makes navigating feel instantaneous by adding a split-second delay b...
New
bencoppock
I write about the frustration that can occur when dealing with string and atom-based keys in maps and how to (safely) translate string-ba...
New
PragTob
I ran into an interesting problem recently where simple concurrency on the BEAM via Task.async made my application a lot slower and a lot...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
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
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
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
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
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
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
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

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement