r00ster

r00ster

Simple form without changeset in Phoenix 1.7

Hi,

I’d like to create a form in Phoenix 1.7 with one text field that has no underlying changeset. Previously I’d use form_for along with text_input. Now, I guess, I should use .simple_form but I don’t know what should be there in the for attribute. When I try to use @conn, as in the 1.6, I get the error: function Plug.Conn.fetch/2 is undefined (Plug.Conn does not implement the Access behaviour. If you are using get_in/put_in/update_in, you can specify the field to be accessed using Access.key!/1).

What is the proper way to create an unbound form in Phoenix 1.7?

Thanks

First Post!

patrickdm

patrickdm

Hello, from the Phoenix.HTML.Form — Phoenix.HTML v4.3.0 docs, it looks like you can use it with a Map:

With map data
form_for/4 expects as first argument any data structure that implements the Phoenix.HTML.FormData protocol. By default, Phoenix.HTML implements this protocol for Map.

This is useful when you are creating forms that are not backed by any kind of data layer. Let’s assume that we’re submitting a form to the :new action in the FooController:

<%= form_for @conn.params, Routes.foo_path(@conn, :new), fn f -> %>
  <%= text_input f, :contents %>
  <%= submit "Search" %>
<% end %>

hth

Most Liked

codeanpeace

codeanpeace

.simple_form is a component defined within the core_components.ex generated via mix phx.gen.new and is a simple wrapper around the Phoenix.Component.form/1 function provided by LiveView.

Using the for attribute

The for attribute can also be a map or an Ecto.Changeset. In such cases, a form will be created on the fly, and you can capture it using :let:

<.form
  :let={form}
  for={@changeset}
  phx-change="change_user"
>

However, such approach is discouraged in LiveView for two reasons:

  • LiveView can better optimize your code if you access the form fields using @form[:field] rather than through the let-variable form
  • Ecto changesets are meant to be single use. By never storing the changeset in the assign, you will be less tempted to use it across operations

source: Phoenix.Component.form/1

GazeIntoTheAbyss

GazeIntoTheAbyss

I’m curious on this as well. Currently I just cheat and make a form component with no changeset. For example for creating a form to sort content based on when it was created I can use the below.
Heex:

  <.form
    let={f}
    id="time-sort-form"
    phx-target={@myself}
    phx-change="update">

    <.input 
      field={f[:time]} 
      type="select" 
      options=
      {
        [ {"Today", "today"}, 
          {"Week", "week"},
          {"Month", "month"},
          {"Year", "year"},
          {"All Time", "all time"}
        ]
      } />

    </.form>
</div>

The ex:

defmodule APP.SortLive.FormComponent do
  use APP, :live_component

  @impl true
  def handle_event("update", %{"time" => time}, socket) do
    username = socket.assigns.user.username
    time = time
    sort = "popular"

    {:noreply, socket |> push_patch(to: ~p"/user/#{username}/?#{[sort: sort, time: time]}")}
  end
end

I pretty much create a form without a changeset, then use a handle_event to deal with the input. I do similar for things like search inputs as well, but my handle_event passes the query value to a function for the redirect.

I’d also like a nice neat example of a changeset free form as I’m pretty sure I’m doing this either wrong or ineffeciently. It works, but feels off.

kokolegorille

kokolegorille

I think it looks like this…

  def mount(_params, _session, socket) do
    {
      :ok,
      socket
      |> assign_form(%{})
    }
  end

You use an empty map as a changeset. Then in the form…

<.simple_form for={@form} as={:form} id="form-graph" phx-submit="save">

As You use normal controllers, it might look a little bit different, especially phx-submit won’t be available. But it should be the way to have a form without changeset.

BTW it’s how phx.gen.auth does when You select non live…

<.simple_form :let={f} for={@conn.params["user"]} as={:user} action={~p"/users/log_in"}>

Last Post!

codeanpeace

codeanpeace

Seems reasonable to me, .form automatically sets the :for assign to %{} by default when it’s not explicitly given.

https://github.com/phoenixframework/phoenix_live_view/blob/0058afeab2f2ad500b7285368c639a0d7bf8f2a0/lib/phoenix_component.ex#L2130-L2138

If you wanted to show which option is currently selected, you could then explicitly set a form assign so that the input component can pull the field value off of the form when generating the options via <%= Phoenix.HTML.Form.options_for_select(@options, @value) %>.

  <.form
    let={f}
    for={@form}
    id="time-sort-form"
    phx-target={@myself}
    phx-change="update">
      <.input 
        type="select" 
        field={f[:time]} 
        options={@time_options}
      />
  </.form>
</div>
defmodule MyApp.SortLive.FormComponent do
  use MyApp, :live_component

  def mount(socket) do
    time_form = to_form(%{"time" => "today"}
    time_options = [{"Today", "today"}, ..., {"All Time", "all time"}]

    {:ok, 
     socket
     |> assign(:sort, "popular")
     |> assign(:time_form, time_form)
     |> assign(:time_options, time_options)}
  end

  def handle_event("update", %{"time" => time}, socket) do
    username = socket.assigns.user.username
    sort = socket.assigns.sort
    time_form = to_form(%{"time" => time})

    {:noreply,
     socket
     |> assign(:time_form, time_form)
     |> push_patch(to: ~p"/user/#{username}/?#{[sort: sort, time: time]}")}
  end
end

Where Next?

Popular in Questions Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

Other popular topics Top

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42716 114
New

We're in Beta

About us Mission Statement