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?
cmo
January 3, 2025, 7:07pm
2
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.