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

davearonson
I’m unit-testing some JS, with Jasmine, and I’d like to check our coverage. We’re not using any front-end framework, nor much JS, so no ...
New
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
Fl4m3Ph03n1x
Background I have a a fresh umbrella project with a Phoenix app inside. To create the app I used the following commands: mix new test_a...
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
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
gameengineer
We are developing on Samsung Tab Active 4 Pro using Android Studio, kotlin and java. We are getting what we think are app deadlocks. The ...
New
avipal
I have an application where it is three layer 1st layer- A legacy core routines 2nd layer- built on top of the Core routine using Dotno...
New
hosseinkhosromanesh
hello , i should code a cluster like image bellow we have no challenge in coding backend but in front need some clue to do this its a dy...
/js
New

Other popular topics Top

dasdom
No chair. I have a standing desk. This post was split into a dedicated thread from our thread about chairs :slight_smile:
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
You might be thinking we should just ask who’s not using VSCode :joy: however there are some new additions in the space that might give V...
New
AstonJ
We have a thread about the keyboards we have, but what about nice keyboards we come across that we want? If you have seen any that look n...
New
AstonJ
There’s a whole world of custom keycaps out there that I didn’t know existed! Check out all of our Keycaps threads here: https://forum....
New
PragmaticBookshelf
Tailwind CSS is an exciting new CSS framework that allows you to design your site by composing simple utility classes to create complex e...
New
PragmaticBookshelf
Author Spotlight Jamis Buck @jamis This month, we have the pleasure of spotlighting author Jamis Buck, who has written Mazes for Prog...
New
PragmaticBookshelf
Author Spotlight Rebecca Skinner @RebeccaSkinner Welcome to our latest author spotlight, where we sit down with Rebecca Skinner, auth...
New
hilfordjames
There appears to have been an update that has changed the terminology for what has previously been known as the Taskbar Overflow - this h...
New
PragmaticBookshelf
Author Spotlight: Peter Ullrich @PJUllrich Data is at the core of every business, but it is useless if nobody can access and analyze ...
New