Learn Phoenix LiveView is the comprehensive tutorial that teaches you everything you need to build a complex, realistic, fully-featured web app with Phoenix LiveView. Click here to learn more!
UPDATE: As of Phoenix v1.7.11, a newly-generated Phoenix app depends on v4.0 of phoenix_html
, which no longer defines form_for/4
at all. That function, along with other HTML tag helpers like link/2
, img_tag/2
, etc., has been extracted to an entirely new package called phoenix_html_helpers
, which you can use if you still need backwards compatibility with those old-style Phoenix helpers. For new code, use <.form>
.
The below article was written when Phoenix still depended by default on phoenix_html
v3.3. While its information about form_for/4
is now out of date, it still has some explanations of <.form>
and <.simple_form>
that may be useful if you’re learning Phoenix.
In older versions of Phoenix, the standard way to render a <form>
tag was with the form_for/4
helper from Phoenix.HTML.Form
:
<%= form_for @changeset, ~p"/comments", fn f -> %>
<%= text_input f, :body %>
<% end %>
(The name form_for
reminds me of the similarly-named helper in Rails, although form_with
is more popular in Rails these days.)
However, since Phoenix LiveView v0.16.0, there’s also the form/1
function component, which does essentially the same thing as form_for/4
:
<.form :let={f} for={@changeset} action={~p"/comments"}>
<.input field={f[:body]} />
</.form>
form/1
was originally defined within Phoenix.LiveView.Helpers
, but v0.18.0 moved it to Phoenix.Component
.
form_for/4
is still available in Phoenix 1.7[1], and the docs still describe this function as “[t]he entry point for defining forms in Phoenix”. So I was confused as to the difference between form_for/4
and form/1
, and when I should prefer one over the other.
One of the many things I love about Elixir and Phoenix is the helpfulness and approachability of their communities. And in this case I managed to get an answer on the Elixir Slack from none other than Phoenix’s creator, Chris McCord:
So that settles it: form_for/4
is deprecated and should no longer be used. I’ve opened a PR to make this clearer in the documentation, and you can stop reading here if that’s all you care about. But there are a few other points about Phoenix 1.7, form/1
, and simple_form/1
that are worth clarifying.
No spam. Unsubscribe any time.
The soft-deprecation of form_for/4
was reflected in a subtle change from Phoenix 1.6 to 1.7.
In a newly-generated Phoenix 1.6 app, your views and components use Phoenix.HTML
, as injected by view_helpers/0
in <your_app>_web.ex
:
defp view_helpers do
quote do
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
…
This runs the macro Phoenix.HTML.__using__/0
, which import
s various modules including Phoenix.HTML.Form
:
@doc false
defmacro __using__(_) do
quote do
import Phoenix.HTML
import Phoenix.HTML.Form
import Phoenix.HTML.Link
import Phoenix.HTML.Tag, except: [attributes_escape: 1]
import Phoenix.HTML.Format
end
end
With this imported, you can call form_for(…)
directly from within views and templates without needing to use the fully-qualified name Phoenix.HTML.Form.form_for
.
In Phoenix 1.7, however, view_helpers/0
changed to html_helpers/0
, and we no longer use Phoenix.HTML
, we merely import
it:
defp html_helpers do
quote do
# HTML escaping functionality
import Phoenix.HTML
…
This imports only those functions that are defined directly within the Phoenix.HTML
module. All the other modules like Phoenix.HTML.Form
aren’t imported by default anymore - so if you want to keep using form_for
, you’ll need to import it yourself:
defmodule YourAppWeb.CommentHTML do
use YourAppWeb, :html
import Phoenix.HTML.Form
def new(assigns) do
~H"""
<%= form_for @changeset, ~p"/comments", fn f -> %>
<%= text_input f, :body %>
<% end %>
"""
end
end
Or you can just write <%= Phoenix.HTML.Form.form_for … %>
. But really you shouldn’t do either - just use <.form>
😉.
form/1
isn’t actually that complicated. It takes a map, changeset, or Phoenix.HTML.Form
struct - the precise differences between these three use cases are explained clearly in the docs - and outputs a <form>
tag.
If you look at the source code, you’ll see that it also outputs up to two hidden inputs:
~H"""
<form {@attrs}>
<%= if @hidden_method && @hidden_method not in ~w(get post) do %>
<input name="_method" type="hidden" hidden value={@hidden_method}>
<% end %>
<%= if @csrf_token do %>
<input name="_csrf_token" type="hidden" hidden value={@csrf_token}>
<% end %>
<%= render_slot(@inner_block, @form) %>
</form>
"""
The first hidden input is used when the form’s method
attribute is something other than GET or POST. Browsers can’t send other types of HTTP request from a <form>
submission, so Phoenix fakes it by submitting a POST request with an additional parameter called _method
whose value is the method we really want, like "patch"
or "put"
.
The server processes this _method
parameter using the module Plug.MethodOverride
. It knows to do this because we tell it explicitly: the plug is called within lib/<your_app>_web/endpoint.ex
:
defmodule YourAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :your_app
…
plug Plug.MethodOverride
…
The second hidden input in <.form>
protects against a common type of attack called cross-site request forgery (CSRF). Phoenix will reject POST requests that don’t contain a valid CSRF token.
Again, this happens explicitly: the :browser
pipeline in the default router includes the protect_against_forgery
plug, which is a thin wrapper around Plug.CSRFProtection:
defmodule YourAppWeb.Router do
use YourAppWeb, :router
pipeline :browser do
…
plug :protect_from_forgery
…
end
…
If you generate code in Phoenix 1.7 with commands like mix phx.gen.html
, you’ll see a new function component being used called simple_form/1
:
<.simple_form :let={f} for={@changeset} action={@action}>
<.error :if={@changeset.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={f[:body]} type="text" label="Body" />
<:actions>
<.button>Save Comment</.button>
</:actions>
</.simple_form>
simple_form/1
doesn’t come directly from your dependencies. It’s defined within YourAppWeb.CoreComponents
, which is included at lib/<your_app>_web/components/core_components.ex
in a newly-generated Phoenix 1.7 app.
CoreComponents.simple_form/1
is a wrapper around <.form>
that provides some basic styling. Since this function is part of your app’s own code, you can customise however you see fit:
def simple_form(assigns) do
~H"""
<.form :let={f} for={@for} as={@as} {@rest}>
<div class="space-y-8 bg-white mt-10">
<%= render_slot(@inner_block, f) %>
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
<%= render_slot(action, f) %>
</div>
</div>
</.form>
"""
end
Personally, I don’t find much use for simple_form/1
and prefer to just write <.form>
directly. But your mileage may vary.
Photo by Scott Graham on Unsplash.
No spam. Unsubscribe any time.