buttonelixirphoenix-live-view

Enable/disable a button in Phoenix 1.7 depending on state


Background

I have a button that may be disabled or not, depending on a set of conditions. I want to disable/enable the button without having to reload the page.

To achieve this I am using the following code:

my_app_live.ex

#provided as a sample
def disable_buttton? do
  if :rand.uniform(100) > 50 do
    "true"
  else
    "false"
  end
end

my_app.html.heex

<.button disabled={disable_button?)}>Execute Command</.button>

core_components.ex

  def button(assigns) do
    extra = assigns_to_attributes(assigns, [:disabled])

    assigns = assign(assigns, :disabled?, case Map.get(extra[:rest], :disabled) do
      "true" -> true
      _ -> false
    end)

    ~H"""
    <p><%= "INSIDE BUTTON: #{inspect(@disabled?)}" %></p>
    <button
      type={@type}
      class={if @disabled? do
      [
        "rounded-md bg-slate-400 px-3 py-2 text-sm font-semibold text-white shadow-sm",
        @class
      ]
    else
      [
        "rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm",
        "active:text-white/80",
        @class
      ]
    end}
      {@rest}
    >
      <%= render_slot(@inner_block) %>
    </button>
    """
  end

Problem

For some reason, <p><%= "INSIDE BUTTON: #{inspect(@disabled?)}" %></p> always shows true and thus the button is always disabled.

In reality disable_buttton? may return true/false depending on which buttons are selected (if all forms have a value, then the button should be enabled), however I am unaware of any pattern to do this in Phoenix.

I am also not convinced I am using assigns properly.

Questions

  1. How can I enable/disable a button in Phoenix, depending on state?
  2. Am I using assigns_to_attributes and assign correctly in this sample?

I read https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#module-attributes but I still don't quite understand what I am missing here.


Solution

  • Issues found

    The main reason my code didn't work was because my disable_button? function was returning "false" instead of false. To be more precise:

    So, in Phoenix LV, if the value is false the attribute disabled is removed from the node, while if it is any other thing (like "false"), the node gets the attribute as disabled="something else", which means that because disabled is still present, irrespective of value, it is considered as a disabled button.

    This differs from the behaviour in the browser, where if I add disable="false" the browser will not consider the node disabled.

    Source: https://elixirforum.com/t/enable-disable-a-button-in-phoenix-1-7-depending-on-state/58007/6?u=fl4m3ph03n1x

    Another issue is that this function also needs to return a value that depends on the state itself. In this case I am generating a random number, which is independent of state, thus the button would never update.

    Answer

    So, for those who are curious, this is the code I ended up with:

    core_components.ex

    This is my modified button function:

      @doc """
      Renders a button.
    
      ## Examples
    
          <.button>Send!</.button>
          <.button phx-click="go" class="ml-2" disabled=false>Send!</.button>
      """
      attr :type, :string, default: nil
      attr :class, :string, default: nil
      attr :disabled, :boolean, default: false
      attr :rest, :global, include: ~w(form name value)
    
      slot :inner_block, required: true
    
      def button(assigns) do
        ~H"""
        <button
          type={@type}
          class={if @disabled do
          [
            "rounded-md bg-slate-400 px-3 py-2 text-sm font-semibold text-white shadow-sm",
            @class
          ]
        else
          [
            "phx-submit-loading:opacity-75 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm",
            "hover:bg-indigo-500 active:text-white/80",
            @class
          ]
        end}
          {@rest}
          disabled={@disabled}
        >
          <%= render_slot(@inner_block) %>
        </button>
        """
      end
    

    And here is how I use it:

    my_app_live.html.heex

    <.button class="min-w-full" disabled={disable_button?(@param1, @state)}>Execute Command</.button>
    

    This depends on the state, because the state changes with time, which means my button's appearance will also change with time!