article

Wednesday, September 10, 2025

Laravel 12 Quiz React | React Starter Kit

Laravel 12 Quiz React | React Starter Kit

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12DB
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

php artisan make:controller HomeController change it with the following codes:
app\Http\Controllers\HomeController.php

//app\Http\Controllers\HomeController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Question;

class HomeController extends Controller
{
    public function get_home_data()
    {
        //$question = Question::latest()->get();
        $question = Question::where('category', "Programming")->latest()->get();
        return Inertia::render('quiz', [
            'questionsitems' => $question,
        ]);

        #return Inertia::render('quiz');
    }
}
Create tables Question Model
php artisan make:model Question -m myapp>php artisan make:model Question -m
Open new Questions migrations yourproject/database/migrations laravelproject\database\migrations\_create_questions_table.php
//laravelproject\database\migrations\_create_questions_table.php
<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('questions', function (Blueprint $table) {
            $table->id();
            $table->string('question');
            $table->json('options');
            $table->string('answer');
            $table->string('category');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('questions');
    }
};
myapp>php artisan migrate
Migration table created successfully.
check database table

update Question Model
app/models/Question.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Question extends Model
{
    protected $fillable = [
        'question',
        'options',
        'answer',
        'category',
    ];

    protected $casts = [
        'options' => 'array', // Automatically casts to/from array/JSON
    ];
}
Frontend with React and InertiaJS
File: pages\quiz.tsx
reactjs\resources\js\pages\quiz.tsx
//reactjs\resources\js\pages\quiz.tsx
'use client';

import { Head } from '@inertiajs/react';
import { useState } from 'react';
import QuestionCard  from "@/components/QuestionCard"
import Confetti from "react-confetti";

interface QuestionItem {
    id: number;
    question: string;
    options: string;
    answer: string;
    category: string;
}
 
interface Props {
    questionsitems: QuestionItem[];
}

export default function quiz({ questionsitems }: Props) {
    //console.log(questionsitems)
    const [currentQuestion, setCurrentQuestion] = useState(0);
    const [selectedAnswer, setSelectedAnswer] = useState(null);
    const [score, setScore] = useState(0);
    const [isFinished, setIsFinished] = useState(false);
    const [showFeedback, setShowFeedback] = useState(false);

    const handleAnswer = (option) => {
        if (showFeedback) return;

        setSelectedAnswer(option);
        setShowFeedback(true);

        if (option === questionsitems[currentQuestion].answer) {
        setScore(score + 1);
        }
    };
    
    const goToNext = () => {
        if (currentQuestion + 1 < questionsitems.length) {
        setCurrentQuestion(currentQuestion + 1);
        setSelectedAnswer(null);
        setShowFeedback(false);
        } else {
        setIsFinished(true);
        }
    };

    const restartQuiz = () => {
        setCurrentQuestion(0);
        setScore(0);
        setSelectedAnswer(null);
        setShowFeedback(false);
        setIsFinished(false);
    };

    const calculateProgress = () => {
        if (isFinished) return 100;
        const baseProgress = (currentQuestion / questionsitems.length) * 100;
        const questionProgress = selectedAnswer ? (1 / questionsitems.length) * 100 : 0;
        return baseProgress + questionProgress;
    };

    const percentage = (score / questionsitems.length) * 100;
    const showConfetti = isFinished && percentage > 50;

    return (
        <div className="min-h-screen">
            <Head title="Quiz"></Head>
            <div className="min-h-screen bg-gray-900 text-white flex flex-col items-center justify-center p-4 ">
            {showConfetti && <Confetti />}    
            <div className="text-center mb-8">
                <h1 className="text-4xl font-bold text-purple-600 mb-2">Laravel 12 Quiz React | React Starter Kit</h1>
                <p className="text-gray-400">Test your knowledge</p>
            </div>
 
            <div className="w-full max-w-xl mb-6">
                <div className="bg-gray-700 h-3 rounded-full overflow-hidden">
                <div
                    className="h-full bg-gradient-to-r from-indigo-500 to-purple-600 duration-500 ease-out transition-all"
                    style={{ width: `${calculateProgress()}%` }}
                ></div>
                </div>
            </div>
           
            {!isFinished ? (
                <>
                <QuestionCard
                    showFeedback={showFeedback}
                    onAnswer={handleAnswer}
                    data={questionsitems[currentQuestion]}
                    current={currentQuestion}
                    total={questionsitems.length}
                    selected={selectedAnswer}
                />
                <div className="mt-6 min-h-[60px]">
                    {showFeedback && (
                    <button
                        className="bg-gradient-to-r from-indigo-600 to-purple-600 py-3 px-6 rounded-lg font-medium shadow-lg cursor-pointer"
                        onClick={goToNext}
                    >
                        {currentQuestion + 1 < questionsitems.length
                        ? "Continue"
                        : "See Results"}
                    </button>
                    )}
                </div>
                </>
            ) : (
                <div className="text-center">
                <h2 className="text-3xl font-bold mb-4">Quiz Completed!</h2>
                <p className="text-xl mb-6">
                    You scored <span>{score}</span> out of{" "}
                    <span className="font-bold">{questionsitems.length}</span> and it is{" "}
                    {Math.round((score / questionsitems.length) * 100)}%
                </p>
                <button
                    className="bg-gradient-to-r from-indigo-600 to-purple-600 py-3 px-6 rounded-lg font-medium shadow-lg cursor-pointer"
                    onClick={restartQuiz}
                >
                    Restart Quiz
                </button>
                </div>
            )}

            </div>
        </div>
    );
}
File: js\components\QuestionCard.tsx
reactjs\resources\js\components\QuestionCard.tsx
//reactjs\resources\js\components\QuestionCard.tsx
const QuestionCard = ({
    data,
    onAnswer,
    showFeedback,
    selected,
    current,
    total,
}) => {
    console.log(data)
    const { question, options, answer } = data;

    const getButtonStyle = (option) => {
        if (!showFeedback) {
        return "bg-indigo-700 hover:bg-indigo-600 hover:scale-[1.01]";
        }
        if (option === answer) return "bg-emerald-600";
        if (option === selected) return "bg-rose-600";
        return "bg-gray-600";
    };

    return (
        <div className="bg-gray-800 p-6 rounded-2xl shadow-lg w-full max-w-xl border border-gray-700">
        <div className="flex justify-between items-center mb-4">
            <h2 className="text-lg font-medium text-gray-300">
            Question {current + 1} of {total}
            </h2>
            <span className="text-sm bg-gray-700 px-3 py-1 rounded-full">
            {selected
                ? Math.round(((current + 1) / total) * 100) + "% complete"
                : Math.round((current / total) * 100) + "% complete"}
            </span>
        </div>
        <p className="text-xl font-medium mb-6">{question}</p>
        <div className="grid gap-3">
            {options.map((option, index) => (
            <button
                className={`${getButtonStyle(
                option
                )} text-left px-4 py-3 cursor-pointer rounded-lg text-white `}
                key={index}
                onClick={() => onAnswer(option)}
                disabled={showFeedback}
            >
                {option}
            </button>
            ))}
        </div>
        </div>
    );
};

export default QuestionCard;
//routes/web.php
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\HomeController;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::get('/quiz', [HomeController::class, 'get_home_data'])->name('quiz');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');
    
});
Run php artisan serve and npm run dev myapp>composer run dev
Starting Laravel development server: http://127.0.0.1:8000

Related Post