phoenixframework / phoenix_html

Building blocks for working with HTML in Phoenix
MIT License
403 stars 220 forks source link

Should Form.input_changed?/3 accept also string fields? #427

Closed dvic closed 1 year ago

dvic commented 1 year ago

https://github.com/phoenixframework/phoenix_html/blob/0d394e90e9e3fd9e63cdbec91f0c572c0ebd64b3/lib/phoenix_html/form.ex#L196-L202

Is there a reason why atoms are only accepted here? FormData.input_value accepts Field.t(), which is atom() | String.t(), right?

josevalim commented 1 year ago

Dup of #408. :)

dvic commented 1 year ago

Is it a dup? Maybe I'm missing something here but that PR and discussion was about Form.fetch/2 right? This is about input_changes? and there's a discrepancy between the field type that is used in both Form.input_value/2 as well as FormData.input_value/3 (both Form.field()).

josevalim commented 1 year ago

input_changed is only used by LV and only for atom Field, which is why I did the connection. But if you have external use cases for it then we can reevaluate!

dvic commented 1 year ago

So the usecase for us is that we're building a dynamic CMS where fields are defined at runtime, so we'd rather not use atoms but strings for the field names. But this is currently not possible as the following gist shows: https://gist.github.com/dvic/1b3d958c1ed9815ec8327cf42ffb13e3

When you click the button you get the following error (i.e., "foo" is not an atom):

19:22:28.058 [error] GenServer #PID<0.388.0> terminating
** (FunctionClauseError) no function clause matching in Phoenix.HTML.Form.input_changed?/3
    (phoenix_html 3.3.2) lib/phoenix_html/form.ex:197: Phoenix.HTML.Form.input_changed?(%Phoenix.HTML.Form{source: %{"foo" => "foo!"}, impl: Phoenix.HTML.FormData.Map, id: nil, name: nil, data: %{}, hidden: [], params: %{"foo" => "foo!"}, errors: [], options: [], index: nil, action: nil}, %Phoenix.HTML.Form{source: %{"foo" => "foo"}, impl: Phoenix.HTML.FormData.Map, id: nil, name: nil, data: %{}, hidden: [], params: %{"foo" => "foo"}, errors: [], options: [], index: nil, action: nil}, "foo")
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/engine.ex:1234: Phoenix.LiveView.Engine.nested_changed_assign?/4
    liveview_access_bug.exs:50: anonymous fn/2 in Example.HomeLive.render/1
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:363: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:538: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
    (elixir 1.15.4) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:361: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:136: Phoenix.LiveView.Diff.render/3
Last message: %Phoenix.Socket.Message{topic: "lv:phx-F3ueTvL6-7mpBAGB", event: "event", payload: %{"event" => "change_item", "type" => "click", "value" => %{"value" => ""}}, ref: "8", join_ref: "4"}
State: %{socket: #Phoenix.LiveView.Socket<id: "phx-F3ueTvL6-7mpBAGB", endpoint: Example.Endpoint, view: Example.HomeLive, parent_pid: nil, root_pid: #PID<0.388.0>, router: Example.Router, assigns: %{form: %Phoenix.HTML.Form{source: %{"foo" => "foo"}, impl: Phoenix.HTML.FormData.Map, id: nil, name: nil, data: %{}, hidden: [], params: %{"foo" => "foo"}, errors: [], options: [], index: nil, action: nil}, items: %{"foo" => "foo"}, __changed__: %{}, flash: %{}, live_action: :index}, transport_pid: #PID<0.386.0>, ...>, components: {%{}, %{}, 1}, topic: "lv:phx-F3ueTvL6-7mpBAGB", serializer: Phoenix.Socket.V2.JSONSerializer, join_ref: "4", upload_names: %{}, upload_pids: %{}}

So it's not per se an external usecase, it's just that it doesn't work currently in liveview :) And I assume the above gist should work?

dvic commented 1 year ago

Especially because you have to define the keys as strings in the map when using to_form. I think it's really confusing for users that they then cannot use string fields as well in the @form[xxx] part.

josevalim commented 1 year ago

Right, now we are going back to #408 which outlines why field does not allow strings. We can make it allow strings, but it should be clear there is no atom conversion.

dvic commented 1 year ago

Right, now we are going back to #408 which outlines why field does not allow strings. We can make it allow strings, but it should be clear there is no atom conversion.

Please bear with me, but I'm really confused now :D What do you mean with 'field does not allow strings'? I assume the "fix" you talk about in #408 was this commit https://github.com/phoenixframework/phoenix_html/commit/3b6e16043e6340154d977b536f549c960d229102? Here you explicitly allow strings right?

dvic commented 1 year ago

So this issue: https://github.com/phoenixframework/phoenix_live_view/issues/2438 it mentions support for @form["field"], to be clear, this is what I want, it does not have to convert it to an atom for me, my FormData implementation will handle the details.

josevalim commented 1 year ago

Oh wow, I completely forgot we actually merged it later on. So you are right, input_changed? should accept strings, correct.

dvic commented 1 year ago

Oh wow, I completely forgot we actually merged it later on. So you are right, input_changed? should accept strings, correct.

Cool! PR coming up ^^

dvic commented 1 year ago

Wait now I'm confused again about the implementation, because the errors are stored as keyword lists right? So we do have to do String.to_existing_atom/1? But this is not what you wanted, correct?

dvic commented 1 year ago

I mean, the docs mention this:

The field name can be either an atom or a string. If it is an atom, it assumes the form keeps both data and errors as atoms. If it is a string, it considers that data and errors are stored as strings for said field.

So I should avoid Keyword.get_values when fetching errors in case of a string field and instead do a manual search?

josevalim commented 1 year ago

Yes, a for-comprehension will do it fine (I think we do it in other places too).