Retrieve param from one handle_event/3 and pass to main when to persist to database

I have a reviews form consisting of a text area and 5 star rating which is persisted in my database. The schema:

schema "reviews" do
    field :content, :string
    field :rating, :integer

    belongs_to :restaurant, LiveWelp.Listing.Restaurant
    belongs_to :user, LiveWelp.Accounts.User
    timestamps(type: :utc_datetime)
  end

My simple form:

<.simple_form for={@form} id="review_form" phx-submit="create_review" phx-change="validate">
  <.input
    field={@form[:content]}
    label="Comment"
    type="textarea"
    required
    placeholder="Your review helps others learn about great local businesses"
  />

  <div class="flex space-x-1">
    <%= for i <- 1..5 do %>
      <svg
        id={"#{i}"}
        xmlns="http://www.w3.org/2000/svg"
        fill="currentColor"
        viewBox="0 0 24 24"
        width="24"
        height="24"
        stroke="currentColor"
        phx-click="rate"
        phx-value-rating={"#{i}"}
        class="cursor-pointer transition-all duration-150 text-gray-500 hover:text-yellow-400 active:text-yellow-400"
      >
        <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
      </svg>
    <% end %>
    <span class="text-gray-500 text-sm"> Select your Rating</span>
  </div>
  <.button>Post Review</.button>

  <.input type="hidden" field={@form[:user_id]} value={@current_user_id} />
  <.input type="hidden" field={@form[:restaurant_id]} value={@restaurant.id} />
  <.input type="hidden" field={@form[:rating]} value="1" />
</.simple_form>

I have a handle_event/3 method for the rating when a star is clicked:

 def handle_event("rate", %{"rating" => rating}, socket) do
    rating = String.to_integer(rating)
    {:noreply, assign(socket, rating: rating)}
  end

But when I submit the form it always defaults to the 1 star value:

%{
  "content" => "fdfdff",
  "rating" => "1",
  "restaurant_id" => "2",
  "user_id" => ""
}

How do I retrieve the clicked star rating when submitting the form?

Map.put(params, "rating", socket.assigns[:rating] || default_rating) in your submit event handler.

You could have a hidden input with value={@rating}.

Might even be able to make each star a radiobox with values 1-5.

You’re storing the rating with assign(socket, rating: rating) in handle_event, but how does that make its way to @form in the markup you posted?

Showing code that touches the :form assign would help people understand what’s happening.

1 Like

The hidden input works, although I find it interesting that I always get a warning when I set <.input> type to hidden <.input type="hidden" field={@form[:rating]} value={@rating} />:

warning: attribute "type" in component LiveWelpWeb.CoreComponents.input/1 must be one of ["checkbox", "color", "date", "datetime-local", "email", "file", "month", "number", "password", "range", "search", "select", "tel", "text", "textarea", "time", "url", "week"], got: "hidden"
    │
 25 │       <.input type="hidden" field={@form[:rating]} value={@rating} />

here’s the markup:

    <.simple_form for={@form} id="review_form" phx-submit="create_review" phx-change="validate">
      <.input
        field={@form[:content]}
        label="Comment"
        type="textarea"
        required
        placeholder="Your review helps others learn about great local businesses"
      />

      <.input type="hidden" field={@form[:user_id]} value={@current_user_id} />
      <.input type="hidden" field={@form[:restaurant_id]} value={@restaurant.id} />
      <.input type="hidden" field={@form[:rating]} value={@rating} />
      <div class="flex space-x-1">
        <%= for i <- 1..5 do %>
          <svg
            id={"#{i}"}
            xmlns="http://www.w3.org/2000/svg"
            fill="currentColor"
            viewBox="0 0 24 24"
            width="24"
            height="24"
            stroke="currentColor"
            phx-click="rate"
            phx-value-rating={"#{i}"}
            class="cursor-pointer transition-all duration-150 text-gray-500 hover:text-yellow-400 active:text-yellow-400"
          >
            <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
          </svg>
        <% end %>
        <br />
      </div>
      <p class="text-gray-500 text-sm">Select your Rating</p>
      <.button>Post Review</.button>
    </.simple_form>

Why not try and make it into a radio-button list? Then you do not need a separate handler.

I’m working on switching to that, I know core components doesn’t have inbuilt support for radio buttons so I added this in my code:

 def input(%{type: "radio"} = assigns) do
    ~H"""
    <div>
      <input
        type="radio"
        id={@id}
        name={@name}
        value={@value}
        class="rounded border-zinc-300 text-zinc-900 focus:ring-0"
        {@rest}
      />
      <.label for={@id}>{@label}</.label>

      <.error :for={msg <- @errors}>{msg}</.error>
    </div>
    """
  end

In my live view I have this:

<div class="rating-stars-wrapper">
          <%= for i <- 5..1//-1 do %>
            <.input
              field={@form[:rating]}
              type="radio"
              name="rating"
              id={"id-#{i}"}
              value={"#{i}"}
              phx-update="ignore"
            />
          <% end %>
        </div>

But this behaves weird, I don’t get the rating passed as review param:

Parameters: %{"_target" => ["rating"], "rating" => "4", "review" => %{"_unused_content" => "", "content" => "", "photo_url" => "", "restaurant_id" => "1", "user_id" => "1"}}

Removing the id value fixes the params issue:

Parameters: %{"review" => %{"content" => "sddfd", "photo_url" => "", "rating" => "5", "restaurant_id" => "1", "user_id" => "1"}}
%{
  "content" => "sddfd",
  "photo_url" => "",
  "rating" => "5",
  "restaurant_id" => "1",
  "user_id" => "1"
}

But I notice that no matter which radio button I select the value is always 5.