Fl4m3Ph03n1x

Fl4m3Ph03n1x

Assigns is not assigning attribute in custom core component

Background

I have a custom component in my LiveView, which is basically a group of checkboxes.
I want to add a new attribute to my custom component, however no matter what I do, I always get nil when accessing said attribute.

Code

core_components.ex

  @doc """
  Generate a checkbox group for multi-select.

  ## Examples

    <.checkgroup
      field={@form[:genres]}
      label="Genres"
      options={[{"Fantasy", "fantasy"}, {"Science Fiction", "sci-fi"}]}
      selected={[{"Fantasy", "fantasy"}]}
    />

  """
  attr :id, :any
  attr :name, :any
  attr :label, :string, default: nil
  attr :field, Phoenix.HTML.FormField, doc: "a form field struct retrieved from the form, for example: @form[:genres]"
  attr :errors, :list
  attr :required, :boolean, default: false
  attr :rest, :global, include: ~w(form readonly)
  attr :class, :string, default: nil
  attr :options, :list, default: [], doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
  attr :selected, :list, default: [], doc: "the currently selected options, to know which boxes are checked"

  attr :meow, :any, default: "meow", doc: "a very cool new attribute!"

  def checkgroup(assigns) do
    new_assigns =
      assigns
      |> assign(:multiple, true)
      |> assign(:type, "checkgroup")

    input(new_assigns)
  end


  def input(%{type: "checkgroup"} = assigns) do
    ~H"""
    <p> <%= "Cat says: #{@meow}" %></p> 

    <div class="mt-2">
      <%= for opt <- @options do %>
        <div class="relative flex gap-x-3">
          <div class="flex h-6 items-center">
            <input id={opt.id} name={@name} type="checkbox" value={opt.id} checked={opt in @selected} disabled={false} />
          </div>
          <div class="text-sm leading-6">
            <label  for={opt.id} ><%= opt.name %></label>
          </div>
        </div>

      <% end %>
    </div>
    """
  end

Here I am using a customer component to render a stylish checkbox group. It works fine, except for the :meow attribute, which somehow is always nil when inside the input function.

Error

Using this code as a sample:

<.checkgroup field={@form[:my_form]} label="SUper question!" options={@sounds} selected={@selected_sounds} meow={[1, 2]} required />

I get the following error upon rendering this code:

** (exit) an exception was raised:
    ** (KeyError) key :meow not found in: %{
  __changed__: nil,
  __given__: %{
    __changed__: nil,
    __given__: %{
      __changed__: nil,
      __given__: %{
        __changed__: nil,
        field: %Phoenix.HTML.FormField{
          id: "my_form",
          name: "my_form",
          errors: [],
          field: :my_form,
          form: %Phoenix.HTML.Form{
            source: %{"some_command" => []},
            impl: Phoenix.HTML.FormData.Map,
            id: nil,
            name: nil,
            data: %{},
            hidden: [],
            params: %{"some_command" => []},
            errors: [],
            options: [],
            index: nil,
            action: nil
          },
          value: nil
        },
        label: "SUper question!",
        meow: [1, 2],
        options: [1, 2, 3, 4],
        # .... more things follow

As you can see, meow: [1, 2], is clearly present. Yet, @meow returns nil and using assigns.meow outright crashes the application.

What am I doing wrong here and how can I fix it?

Marked As Solved

Fl4m3Ph03n1x

Fl4m3Ph03n1x

After a lot of digging around, i realized that all of the input functions incore_components.ex modify assigns. The best example of this is this intermediary function, called before any input component functions:

  def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
    assigns
    |> assign(field: nil, id: assigns.id || field.id)
    |> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
    |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
    |> assign_new(:value, fn -> field.value end)
    |> input()
  end

This means I do not fully control what is going on. I decided to move away from using/modifying the input functions and created my own component from scratch:

@doc """
  Generate a checkbox group for multi-select.

  ## Examples

    <.checkgroup
      field={@form[:genres]}
      label="Genres"
      options={[{"Fantasy", "fantasy"}, {"Science Fiction", "sci-fi"}]}
      selected={[{"Fantasy", "fantasy"}]}
    />

  """
  attr :name, :any

  attr :field, Phoenix.HTML.FormField,
    doc: "a form field struct retrieved from the form, for example: @form[:genres]"

  attr :required, :boolean, default: false

  attr :options, :list,
    default: [],
    doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"

  attr :disabled, :list, default: [], doc: "the list of options that are disabled"

  attr :selected, :list,
    default: [],
    doc: "the currently selected options, to know which boxes are checked"

  def checkgroup(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
    assigns =
      assigns
      |> assign(:name, "#{field.name}[]")

    ~H"""
    <div class="mt-2">
      <%= for opt <- @options do %>
        <div class="relative flex gap-x-3">
          <div class="flex h-6 items-center">
            <input
              id={opt.id}
              name={@name}
              type="checkbox"
              value={opt.id}
              class={
                if opt in @disabled do
                  "h-4 w-4 rounded border-gray-300 text-gray-300 focus:ring-indigo-600"
                else
                  "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
                end
              }
              checked={opt in @selected}
              disabled={opt in @disabled}
            />
          </div>
          <div class="text-sm leading-6">
            <label
              for={opt.id}
              class={
                if opt in @disabled do
                  "text-base font-semibold text-gray-300"
                else
                  "text-base font-semibold text-gray-900"
                end
              }
            >
              <%= opt.name %>
            </label>
          </div>
        </div>
      <% end %>
    </div>
    """
  end

which works as expected and all the assigns not have the expected values.

Where Next?

Popular Frontend topics Top

pillaiindu
Some days ago I came across a video teaching the internals of git. It had some nice diagrams and animations. The diagrams looked like han...
New
The_Exile
I am new to programming. I started reading Eloquent Javascript 3rd Edition, as the book comes highly recommended as a good place for beg...
/js
New
pillaiindu
I mean, when you render all the HTML at the server side and the data is sent through HTTP requests, except only if some tiny things are d...
New
beberardinelli
Hi! I just started coding a few months ago and I am trying to get all the help I can get. My friend showed me this debugging tool called...
New
palak231
Hi this is Palak Sharma I am new here and I found this community while researching about JavaScript over the internet. Well I have comp...
/js
New
sona11
I have a 1D array of numbers and need help splitting them into groups using a jagged array so that I can perform a series of computations...
New
Fl4m3Ph03n1x
Background I have a Phoenix application, where all pages (expect the login page) have a menu at the top. This menu will therefore only a...
New
harwind
I’m currently working on a front-end development project and I’m facing an issue with aligning items using CSS Flexbox. I want to horizon...
New
pjamesrud
I am creating an app that allows user to enter values and depending on the value a number of textviews are changed programmatically from ...
New
selamibaba
I maintain a content website built around a fixed domain of 78 unique items. This is the current overview page showing the complete data...
New

Other popular topics Top

PragmaticBookshelf
Stop developing web apps with yesterday’s tools. Today, developers are increasingly adopting Clojure as a web-development platform. See f...
New
AstonJ
poll poll Be sure to check out @Dusty’s article posted here: An Introduction to Alternative Keyboard Layouts It’s one of the best write-...
New
New
New
AstonJ
Do the test and post your score :nerd_face: :keyboard: If possible, please add info such as the keyboard you’re using, the layout (Qw...
New
Exadra37
I am asking for any distro that only has the bare-bones to be able to get a shell in the server and then just install the packages as we ...
New
PragmaticBookshelf
Learn different ways of writing concurrent code in Elixir and increase your application's performance, without sacrificing scalability or...
New
DevotionGeo
The V Programming Language Simple language for building maintainable programs V is already mentioned couple of times in the forum, but I...
New
AstonJ
Was just curious to see if any were around, found this one: I got 51/100: Not sure if it was meant to buy I am sure at times the b...
New
CommunityNews
Open-source implementation of the classic GTA engine now running directly in your browser. Experience the reVC technology demo on DOS.Zon...
New