Star Rating Component using Livewire & AlpineJS

Tailwind CSS Alpine JS Livewire

Demo

Star Rating Component Example

Card image cap

New Bootstrap 5 Livewire

Simple Livewire toggle switch

This is a pro component. Get Livewiredemos pro access to view / download the component code.

Livewire Component Class


<?php

namespace App\Http\Livewire\Pro;

use Livewire\Component;
use Illuminate\Database\Eloquent\Model;

class StarRatingComponent extends Component
{
    public $rating = 0, $previousRating = 0;
    public Model $model;

    public function mount()
    {
        if (auth()->check()) {
            $this->rating = $this->ratings_query->first()->rating ?? 0;
            $this->previousRating = $this->rating;
        }
    }

    public function render()
    {
        return view('livewire.pro.star-rating-component', [
            'total_ratings' => $this->total_ratings,
            'average_rating' => $this->average_rating,
        ]);
    }

    public function updatedRating()
    {
        if ($this->rating == 0) {
            $this->ratings_query->where('rating', $this->previousRating)->delete();

            $this->reset([
                'rating',
            ]);

            return;
        }

        $this->model->ratings()->updateOrCreate(
            ['user_id' => auth()->id()],
            [
                'rating' => $this->rating,
            ]
        );

        $this->previousRating = $this->rating;
    }

    public function getAverageRatingProperty()
    {
        return number_format($this->model->ratings()->avg('rating'), 1);
    }

    public function getTotalRatingsProperty()
    {
        $this->model->refresh();

        return $this->model->ratings->count();
    }

    public function getRatingsQueryProperty()
    {
        return auth()->user()->rating()->where('rateable_type', get_class($this->model))->where('rateable_id', $this->model->id);
    }
}


Component View File


<div class="flex w-full justify-center items-center">
    <div x-data="{
        rating: @entangle('rating'),
        hoverRating: 0,
        ratings: [{ 'amount': 1 }, { 'amount': 2 }, { 'amount': 3 }, { 'amount': 4 }, { 'amount': 5 }],
        rate(amount) {
            if (this.rating == amount) {
                this.rating = 0;
            } else this.rating = amount;
        },
    }"
    x-init="hoverRating = rating"
        class="flex flex-col items-center justify-center space-y-2 rounded w-full bg-gray-200 mx-auto">
        <div class="my-2">
            Average rating: <b>{{ $average_rating }} / 5 ({{ $total_ratings }} votes)</b>
        </div>
        <div class="flex space-x-0 bg-gray-100">
            <template x-for="(star, index) in ratings" :key="index">
                @auth
                    <button @click="rate(star.amount)" @mouseover="hoverRating = star.amount"
                        @mouseleave="hoverRating = rating" aria-hidden="true"
                        class="rounded-sm fill-current focus:outline-none focus:shadow-outline p-1 w-12 m-0 cursor-pointer"
                        :class="{'text-gray-600': hoverRating >= star.amount, 'text-yellow-400': rating >= star.amount && hoverRating >= star.amount}">
                        <svg class="w-15 transition duration-150" xmlns="http://www.w3.org/2000/svg"
                            viewBox="0 0 20 20" fill="currentColor">
                            <path
                                d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                        </svg>
                    </button>
                @else
                    <button @click="Livewire.emitTo('internal.login-register-component','showModal')"
                        @mouseover="hoverRating = star.amount" @mouseleave="hoverRating = rating" aria-hidden="true"
                        class="rounded-sm text-gray-400 fill-current focus:outline-none focus:shadow-outline p-1 w-12 m-0 cursor-pointer"
                        :class="{'text-gray-600': hoverRating >= star.amount, 'text-yellow-400': rating >= star.amount && hoverRating >= star.amount}">
                        <svg class="w-15 transition duration-150" xmlns="http://www.w3.org/2000/svg"
                            viewBox="0 0 20 20" fill="currentColor">
                            <path
                                d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                        </svg>
                    </button>
                @endauth
            </template>
        </div>
    </div>
</div>

Usage


@livewire('pro.star-rating-component', [
                            'model' => $post
                            ])

Documentation

You'll need to have a Model and database table to store Ratings in the database.


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateRatingsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('ratings', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained('users');
            $table->string('rateable_id');
            $table->string('rateable_type');
            $table->smallInteger('rating');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('ratings');
    }
}

Rating Modal


<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Rating extends Model
{
    use HasFactory;

    protected $fillable = [
        'user_id',
        'rateable_id',
        'rateable_type',
        'rating',
    ];

    public function rateable()
    {
        return $this->morphTo();
    }
}

Add the following relationship to the Modal which you are looking to rate.


    public function ratings()
    {
        return $this->morphMany(Rating::class, 'rateable');
    }