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

ClaudiaGiv
I have a sveltekit project that is using GoTrue library for authentication. In development mode (npm run dev) everything works but when I...
New
jubocade
What is the best course of front end (live webinars or recorded)? So I already have basic understanding of HTML CSS JS and React but I wa...
New
axelson
In Elixir I love to use the library GitHub - sasa1977/boundary: Manage and restrain cross-module dependencies in Elixir projects to enfor...
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
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
tomcatttttt
I’m trying to take the API from the site to get a seamless online game through the HTML5 API. It works in all browsers except Google Chro...
New
Fl4m3Ph03n1x
Background I have created a fresh Phoenix app using mix phx.new.web web_interface --no-dashboard --no-ecto --no-gettext --no-mailer insid...
New
JessicaW33
Hello All, How would you implement a navigation system in React Native? Could not find a way so I ask the community can someone help me ...
New
jaeyson
would it be backwards if I’m learning react? last time I used UI library (aside from LiveView) was Elm (not much) and that was eons ago.
New
SteelFork2819
hi does anyone know how to render a cloud-stored 3D file and move a camera around in it using native html5 functions? i really don’t know...
New

Other popular topics Top

New
PragmaticBookshelf
Andy and Dave wrote this influential, classic book to help their clients create better software and rediscover the joy of coding. Almost ...
New
PragmaticBookshelf
Brace yourself for a fun challenge: build a photorealistic 3D renderer from scratch! In just a couple of weeks, build a ray tracer that r...
New
AstonJ
Curious to know which languages and frameworks you’re all thinking about learning next :upside_down_face: Perhaps if there’s enough peop...
New
AstonJ
I ended up cancelling my Moonlander order as I think it’s just going to be a bit too bulky for me. I think the Planck and the Preonic (o...
New
Exadra37
Oh just spent so much time on this to discover now that RancherOS is in end of life but Rancher is refusing to mark the Github repo as su...
New
foxtrottwist
A few weeks ago I started using Warp a terminal written in rust. Though in it’s current state of development there are a few caveats (tab...
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
AstonJ
Curious what kind of results others are getting, I think actually prefer the 7B model to the 32B model, not only is it faster but the qua...
New
Fl4m3Ph03n1x
Background Lately I am in a quest to find a good quality TTS ai generation tool to run locally in order to create audio for some videos I...
New