React Quiz App

Do not miss this exclusive book on Binary Tree Problems. Get it now for free.

In this article at OpenGenus, we have explored how we can develop an online quiz app with reactjs. A quiz app is a software application designed to present a series of questions to the user in order to test their knowledge or understanding of a particular subject. I have built a react based app that has two categories and shows a list of questions and the user will have multipe questions to answer in 3 minutes else the app will show the current score and terminate the quiz and at the end show the score or the result of the quiz following some inforamtions. Links to GitHub repo and Live link can be found at the bottom of this page.

Flow of application

We weil have a home page where the user enters there name and selects the category that they are intersted in taking the quiz. After that the app will present 10 question one after another from a local JSON file that holds question for exmaple techQuestions.json and geoQuestions.json we can also add more questions or categories and only the user can see the next question when they answer the current question. When the user is done with the last question the app will take them to the score page where they can see there socre. To make the quiz app exciting time is also added so quiz takers will have to complete the quiz within the given time i.e. 4 min.

Important Packages and functions.

In order to work on this project basic knowledge of Vite + React + TypeScript is required and familiarity with the following packages

  1. Vite
  2. TailwindCSS
  3. React countdown

We will be using TypeScript in this project using TypeScript in a quiz app project can be highly beneficial due to several reasons. Firstly, TypeScript provides static type-checking, which can help catch errors before they occur during runtime. This means that the app can be more robust and less prone to errors, ensuring a better user experience. Moreover, with TypeScript, the codebase can be more maintainable and scalable, as it allows for better code organization and helps developers understand the codebase more easily. This can save time and effort during development, making the project more efficient.

Additionally, TypeScript offers several features that can be particularly useful in a quiz app project. For example, with TypeScript's type system, it is possible to define more complex data structures that may be required in a quiz app, such as questions and answer choices. Furthermore, TypeScript can help improve code readability and reduce code duplication by enabling the use of interfaces and enums. This can make the code more structured and easier to work with, especially in larger projects. Overall, using TypeScript in a quiz app project can lead to a more reliable and efficient development process, resulting in a better end product for users.

1. Vite

Vite is a build tool for modern web applications, including those built with React. It aims to provide a faster development experience by using modern browser APIs and features such as ES modules, HMR (Hot Module Replacement), and a lightning-fast development server.

With Vite, you can build and serve your React application locally in development mode, with instant page refreshes and optimized build times. This is achieved by leveraging ES modules and the browser's native module loading capabilities to import and process code on demand, without requiring a full build step.

Overall, Vite provides an efficient and modern toolchain for React development, enabling developers to build faster and more optimized web applications.

2. TailwindCSS

Tailwind CSS is a utility-first CSS framework that provides a set of pre-designed, ready-to-use classes to style HTML elements. It is designed to provide a low-level, customizable approach to building user interfaces, allowing developers to rapidly create modern, responsive designs with minimal CSS code.

3. React countdown

React Countdown is an NPM package that provides a React component for displaying a countdown timer on a web page. This component can be used to add a countdown timer to a variety of applications, such as event pages, e-commerce websites, and more.

Now lets talk about some of the compoentns that are used in the quiz app

Home.tsx

The Home.ts file defines a functional component called "Home" that displays the home page of the quiz app. It uses the "useNavigate" hook from React Router to declare a constant "navigate" that can be used to navigate to different pages of the app. It also declares state variables "name", "quiz", "category", and "isdisabled" using the "useState" hook. The "handlequiz" function sets "quiz" to false and navigates to the quiz page when the "Take Quiz" button is clicked. The "handlecategory" function updates "category" based on the value of the select input. The component contains a conditional expression that displays either the input and select fields or nothing, depending on the value of "quiz". When the user enters their name, the "useEffect" hook sets "isdisabled" to false, enabling the "Take Quiz" button. The component returns a JSX expression that contains a div with classes that center its contents horizontally and vertically, an image tag with a source and classes that style it, and the conditional expression.

const Home = () => { // defining a functional component called "Home"
    const navigate = useNavigate() // declaring a constant "navigate" using the "useNavigate" hook from React Router
    const [name, setName] = useState('') // declaring a state variable "name" and a function to update it "setName" using the "useState" hook
    const [quiz, setQuiz] = useState(true) // declaring a state variable "quiz" and a function to update it "setQuiz" using the "useState" hook
    const [category, setCategory] = useState<string>('tech'); // declaring a state variable "category" of type string and a function to update it "setCategory" using the "useState" hook
    const [isdisabled, setisdisabled] = useState(true); // declaring a state variable "isdisabled" and a function to update it "setisdisabled" using the "useState" hook
    const handlequiz = () => { // defining a function "handlequiz" that sets "quiz" to false and navigates to the quiz page using the "navigate" constant
        setQuiz(false)
        navigate(`/quiz/${category}/${name}`, {state: { category, name}})
    }
    useEffect(() => { // declaring a side effect that sets "isdisabled" to false if "name" is not empty and to true otherwise
        if(name){
            setisdisabled(false);
        }
        else{
            setisdisabled(true);
        }
    }, [name])
    const handlecategory = (e: any) => { // defining a function "handlecategory" that updates "category" based on the value of the select input
        setCategory(e.target.value)
        
    }


    return ( // returning a JSX expression
        <>
        <div className="flex items-center justify-center bg-[#262626]"> {/* a div with classes that center its contents horizontally and vertically */}
            
            <div><img className=" w-[70%] h-[350px] rounded-2xl" alt="quiz home" src={Quizhome}/></div> {/* an image tag with a source and classes that style it */}
            <div> {/* a div that contains a conditional expression */}
            {quiz ?  // if quiz is true
            <div className="flex flex-col items-center my-36 justify-center gap-8"> {/* a div with classes that align its contents vertically */}
                <div className="flex gap-5"> {/* a div with classes that space its contents horizontally */}
            <input className="w-72 border p-5" type="text" placeholder="Enter your name" onChange={(e) => setName(e.target.value)} /> {/* an input tag with a placeholder, a class, and an onChange event listener that sets "name" to the input's value */}
            <select title="Select Category" className="w-28 text-center h-14 bg-green-300 rounded-lg" onChange={handlecategory}> {/* a select tag with a title, a class, and an onChange event listener that calls "handlecategory" */}
                <option value="tech">Technology</option> {/* an option tag with a value */}
                <option value="geo">Geography</option>        {/* an option tag with a value */} 
            </select>
            </div>
            <small className="text-white">Note: You will have 10 multiple-choice questions to finish in 4 minutes!</small> {/* a small tag with a class */}
                 <button disabled={isdisabled} className="text-white bg-green-500 p-3 rounded-xl w-[30%] disabled:bg-gray-600" onClick={handlequiz}>Take Quiz</button> 
                 </div> 
                : 
                <div>
                </div>
                  }
        </div>
        </div>
        </>
    )
}


Quizes.tsx

const Quizs = () => {
  const navigate = useNavigate(); // useNavigate hook for navigation
  const { category, name } = useParams(); // get category and name from URL params
  const [currentqt, setcurrentqt] = useState(0); // state to track current question number
  const [score, setscore] = useState(0); // state to track score
  const [isdisabled, setisdisabled] = useState(true); // state to disable next button until an answer is selected
  const [selected, setselected] = useState(); // state to track selected answer
  useEffect(() => {
    // effect to update score in URL
    const encodeScore = btoa(score.toString()); // encode score as base64
    const urlSearchParams = new URLSearchParams(window.location.search); // create URLSearchParams object
    urlSearchParams.set("quiz", encodeScore); // set quiz parameter in URL with encoded score
    window.history.replaceState({}, "", `${window.location.pathname}?${urlSearchParams}`); // update URL without reloading
  }, [score]);
  const answers = (answer: any) => {
    // function to handle answer selection
    setselected(answer); // update selected answer state
    setisdisabled(false); // enable next button
  };
  const handleNextClick = (e: any) => {
    // function to handle next button click
    setisdisabled(true); // disable next button
    e.preventDefault(); // prevent default form submission behavior
    setcurrentqt(currentqt + 1); // update current question number
    if (category == "tech") {
      // check category to determine which questions to use
      if (selected === techQuestions[currentqt].correct) {
        // check if selected answer is correct
        setscore(score + 1); // update score if correct
      }
    } else {
      if (selected === geoQuestions[currentqt].correct) {
        setscore(score + 1);
      }
    }
  };
  if (currentqt == 10) {
    // if all questions have been answered, navigate to score page
    navigate(`/score/${score}/${name}`, { state: { score: score, name: name } });
  }
  return (
    <>
      <div className="relative flex gap-7 justify-center p-2 bg-gray-200">
        <p>Hello {name}</p>
        <p>Your Quiz category is {category == "geo" ? "Geography" : "Technology"}</p>
      </div>
      <div className="relative">
        <h2 className="bg-gray-200">Quiz 10 questions</h2>
        <Time />
        <h3 className=" bg-green-500 w-24 rounded-xl text-2xl p-5 absolute top-[62px]   ">{currentqt + 1}/10</h3>
        {currentqt < 10 ? (
          category === "tech" ? (
            <div>
              <form>
                <Quiz
                  handleCallback={answers}
                  key={currentqt}
                  Question={techQuestions[currentqt]}
                  questionId={0}
                  question={""}
                  answers={[]}
                  correct={""}
                  isanswered={undefined}
                  handlesetScore={undefined}
                  category={""}
                  name={""}
                />
                <div className="flex justify-end ">
                  <button
                    disabled={isdisabled}
                    type="submit"
                    name="tech"
                    className="bg-green-500 text-white px-8 py-2 rounded-md hover:scale-110 mt-[-27px] disabled:bg-gray-400 
                     <img src={nextbutton} className='w-8' alt="next-button" />
                            </button>
                            </div>
                        </form>
                    </div> 
                    
                    :
                    <div>
                        <form>
                        <Quiz handleCallback={answers} key={currentqt} Question={geoQuestions[currentqt]} questionId={0} question={''} answers={[]} correct={''} isanswered={undefined} handlesetScore={undefined} category={''} name={''} /> //Passing the the answers function so that when the user selects the answer method updates the selected state to later evaluate if  the correct answer is given.
                        <div className="flex justify-end ">
                            <button disabled={isdisabled} type='submit' name="geo" className=" bg-green-500 text-white px-8 py-2 rounded-md hover:scale-110 mt-[-27px] disabled:bg-gray-400" onClick={handleNextClick}>
                                <img src={nextbutton} className='w-8' alt="next-button" />
                            </button>
                        </div>
                        </form>
                    </div>
                : <></>
                }
            </div>
            <div>
            </div>
        </>
    )
}
                    "

The component below is a React component that displays a single question and its answer choices. It receives the question and answer data as props and maps over the answer choices to display them as radio buttons. When a user selects an answer choice, the handleanswer function is called and the selected value is passed to the parent component via a callback function. The component uses CSS styling classes to create a border, shadow, and background color.
Quiz.tsx


// Component to display a single question and its answer choices
const Quiz = (props: Question) => {
  // Handle when user selects an answer choice
  const handleanswer = (e: any) => {
    props.handleCallback(e.target.value);
  };

  return (
    <div className="border flex flex-col p-7 shadow-xl bg-gray-200 mt-20">
      <div className="flex">
        {/* Display the question */}
        <p className="text-2xl">{props.Question.question}</p>
      </div>
      <div className="flex flex-col items-start gap-4  text-xl  p-20">
        {/* Map over the answer choices and display them as radio buttons */}
        {props.Question.answers.map((answer: string, i: number ) => {
          return (
            <ul className="flex " key={i}>
              <li className="flex border bg-gray-300 answered w-[100%] items-center p-3 rounded-xl"  >
                <input
                  onChange={handleanswer}
                  value={answer}
                  type="radio"
                  name="ans"
                  className="mr-4 w-4 h-4 "
                />
                {/* Display the answer choice */}
                {answer}
              </li>
            </ul>
          );
        })}
      </div>
    </div>
  );
};

The component below called Score that retrieves data from the URL parameters using the useParams hook provided by react-router-dom. It receives three parameters: score, name, and info, which are used to display personalized feedback to the user based on their quiz results. The score parameter is converted from a string to a number using the Number() function to facilitate comparison operations. The component then renders a personalized message to the user based on their score and provides a button to try the quiz again or change the category.

The component uses conditional rendering with the ternary operator ? to display different messages and styles based on the user's score. If the user's score is greater than or equal to 8, the component displays a congratulatory message with a green background color and additional information about the user's score. If the score is between 6 and 7, the component displays a message with a yellow background color and encourages the user to try again. If the score is less than or equal to 5, the component displays a message with a red background color and encourages the user to try again. Finally, a button is provided to allow the user to try the quiz again. The useNavigate hook is used to handle navigation to the home page when the button is clicked.

Score.tsx

import { useNavigate } from 'react-router-dom'; // Import the `useNavigate` hook from `react-router-dom`.
import { useParams } from 'react-router-dom'; // Import the `useParams` hook from `react-router-dom`.

const Score = () => { // Declare a new component called `Score`.
  const { score, name, info } = useParams<{ score: string, name: string, info: string }>(); // Retrieve the URL parameters `score`, `name`, and `info` using the `useParams` hook.
  const scoreNumber = Number(score); // Convert the `score` parameter from a string to a number using the `Number()` function.
  const navigate = useNavigate(); // Declare a new variable `navigate` that uses the `useNavigate` hook.

  console.log("info", info); // Log the `info` parameter to the console.

  return ( // Return a JSX expression.
    <div className='mt-36'>
      <h2>{info}</h2> // Render the `info` parameter as a heading.
      {scoreNumber >= 8 ? // Use conditional rendering to display different messages and styles based on the `scoreNumber`.
        <>
          <div className="celebration">
            <h1>Congratulations!</h1>
            <p>You got a great score!</p>
          </div>
          <div className='bg-green-400 text-white p-5'>
            <h1 className='text-2xl font-bold'>
              Congrats! {name} You scored the higest 
              Your Score is <i>{score}</i> out of 10
            </h1>
            <button className='p-5 bg-slate-800 text-white rounded-3xl mt-5 hover:text-black hover:bg-white' onClick={() => {navigate('/')}}>Try agin or Change Category</button>
          </div>
        </>
        :
        <>
          {(scoreNumber <= 7 && scoreNumber > 5) ?
            <div className='text-2xl font-bold bg-yellow-400 text-white p-5'>
              <h1>Good job! {name} Try again
                  You Scored <i>{score}</i> out of 10   
              </h1>
            </div>
            :
            <h1></h1>
          }
          {scoreNumber <= 5 ?
            <div className='text-2xl font-bold bg-red-400 text-white p-5'>
              <h1>Nice try! {name} Try again
                  You Scored <i>{score}</i> out of 10   
              </h1>
            </div>
            :
            <h1></h1>
          }
          <button className='p-5 bg-slate-800 text-white rounded-3xl mt-5' onClick={() => {navigate('/')}}>Try agin</button>
        </>
      }
    </div>
  )
}

export default Score; // Export the `Score` component.


Time.tsx
The "Time" component is responsible for displaying a countdown timer on the screen and redirecting the user to the score page if they run out of time. The component uses the "react-countdown" library to display the time remaining in hours, minutes, and seconds.

The component also uses the "useParams" hook to retrieve the player's name from the URL and the "useNavigate" hook to redirect the user to the score page. If the player runs out of time, the "Completionist" function will be called to redirect the user to the score page. The "Completionist" function decodes the score from the URL and passes it to the score page along with the player's name and a message indicating that the player ran out of time.

Overall, the "Time" component is a crucial part of the quiz app because it ensures that players have a set amount of time to complete each question, and it helps to create a sense of urgency and excitement for the players.

import React from 'react'; // Importing React library
import Countdown from 'react-countdown'; // Importing Countdown component from 'react-countdown' library
import { useNavigate } from 'react-router-dom'; // Importing useNavigate hook from 'react-router-dom' library
import { useParams } from 'react-router-dom'; // Importing useParams hook from 'react-router-dom' library

const Time = React.memo(() => { // Defining Time component as a functional component that is memoized using React.memo()
  const {name} = useParams() // Extracting the name parameter from the URL using useParams() hook
  const urlSearchParams = new URLSearchParams(window.location.search); // Creating a URLSearchParams object based on the URL query string
  let quiz = urlSearchParams.get('quiz') // Extracting the quiz parameter from the URL query string using get() method
  
  const navigate = useNavigate(); // Using useNavigate() hook to create a navigate function to move to another page
  const Completionist = () => // Defining a Completionist component which will be used when the countdown timer is completed
  {
    if(quiz===null){ // Checking if the quiz parameter is null
      quiz="0"; // If it's null, set quiz to 0
    }
    const decodeScore = parseInt(atob(quiz), 10) // Decoding the quiz parameter value from base-64 and parsing it as an integer
    setTimeout(() =>{ // Setting a timeout of 1 second before navigating to the score page
      navigate(`/score/${decodeScore}/${name}`, {state: {score: decodeScore, name: name, info: "Time is up"}})
    }, 1000)
    return (<span>Time is up</span>) // Displaying "Time is up" when the countdown timer is completed
  };

  const renderer = ({ hours, minutes, seconds, completed }: any) => { // Defining a renderer function which will be used to display the countdown timer
    if (completed) { // Checking if the countdown timer is completed
      // Render a completed state
      return <Completionist />; // If it's completed, render the Completionist component
    } else {
      // Render a countdown
      return <span>{hours}:{minutes}:{seconds}</span>; // If it's not completed, render the countdown timer with hours, minutes, and seconds
    }
  };
   
  return (
    <div>
      <h2 className=' float-right'> // Displaying the time left in the top right corner of the page
        Time left 
        <span className='text-green-500'> 
          <Countdown // Displaying the countdown timer using the Countdown component from 'react-countdown' library
            date={Date.now() + 180000} // Setting the countdown timer to 3 minutes (180000 milliseconds) from the current time
            renderer={renderer} // Using the renderer function to display the countdown timer
          />
        </span>
      </h2>
    </div>
  )
});

export default Time // Exporting the Time component so that it can be used in other components or files

Link to GitHub repository

Sign up for FREE 3 months of Amazon Music. YOU MUST NOT MISS.