How to Build an Interactive Quiz App with React and TypeScript

React and TypeScript are two powerful tools that, when combined, enable developers to build robust and maintainable web applications. In this tutorial, we‘ll harness their strengths to create an interactive quiz app from scratch.

React has taken the web development world by storm with its component-based architecture and virtual DOM implementation. It allows you to break your UI down into reusable pieces and efficiently update the UI as the underlying data changes. TypeScript, on the other hand, is a typed superset of JavaScript that catches errors at compile-time rather than runtime. It makes your code more predictable and easier to reason about, especially as your app grows in size and complexity.

Together, React and TypeScript provide a solid foundation for building modern web apps. Let‘s put them to use to build a quiz app that tests users‘ knowledge and provides instant feedback. We‘ll fetch the quiz questions from an API, track the user‘s score, and even throw in some slick animations. By the end, you‘ll have a good grasp of how to structure a React+TS project and implement common quiz features.

Setting Up the Project

To get started, we‘ll use the create-react-app CLI to bootstrap a new project with TypeScript support:

npx create-react-app my-quiz-app --template typescript

This sets up a basic project structure and installs the necessary dependencies, including React, TypeScript, and webpack. The –template typescript flag preconfigures the project with TypeScript support.

After the setup script finishes, navigate into the project folder and start the development server:

cd my-quiz-app
npm start

You should see the default React app page when you open http://localhost:3000 in your browser. We‘ll replace this with our quiz app UI.

Designing the Quiz Components

Let‘s think about the main pieces we‘ll need for our quiz app:

  1. A Quiz component that holds the questions, current score, and other quiz state. This will be the "root" component that manages the overall quiz flow.

  2. A QuizQuestion component to display the current question and answer choices. We‘ll pass the question data to this component as props from the Quiz component.

  3. A QuizResult component to display the final score and a button to retake the quiz. This will be shown when the user has answered all the questions.

  4. Styled components for the UI layout and styles. We‘ll use the styled-components library to write our CSS directly in our component files.

Here‘s a rough sketch of how these components will fit together:

<Quiz>
  {isQuizOver ? (
    <QuizResult score={score} restartQuiz={restartQuiz} />
  ) : (
    <>
      <QuizQuestion 
        question={currentQuestion} 
        onAnswer={handleAnswer}
      />
      <QuizProgress 
        currentQuestionIndex={currentQuestionIndex}
        totalQuestions={questions.length}
      />
    </>
  )}
</Quiz>

The Quiz component will conditionally render either the QuizQuestion component or the QuizResult component depending on whether the quiz is over. It will also keep track of the current question index and pass the current question data down to QuizQuestion.

Fetching Quiz Data

For the sake of simplicity, we‘ll fetch our quiz questions from the Open Trivia Database API. It provides a wide selection of questions across various categories that we can use in our app.

To fetch the data, we‘ll use the built-in fetch function and specify the URL with our desired parameters:

const fetchQuizQuestions = async (amount: number, difficulty: Difficulty): Promise<QuestionState[]> => {
  const endpoint = `https://opentdb.com/api.php?amount=${amount}&difficulty=${difficulty}&type=multiple`;
  const data = await (await fetch(endpoint)).json();
  return data.results.map((question: Question) => ({
    ...question,
    answers: shuffleArray([...question.incorrect_answers, question.correct_answer])
  }))
};

This function takes in the desired number of questions and difficulty level, makes a request to the API endpoint, and returns a promise that resolves to an array of formatted question objects.

Note how we specify the return type as Promise<QuestionState[]> to indicate this is an async function that returns a promise resolving to an array of QuestionState objects. We‘ll define the QuestionState type to match the structure of the question data we need in our app:

type QuestionState = {
  question: string;
  answers: string[];
  correct_answer: string;
};

By specifying types for the API response data and our app state, TypeScript can help us catch any discrepancies and ensure we‘re working with the data we expect.

Implementing the Quiz Logic

Now that we have our quiz data, let‘s implement the core logic for iterating through the questions and tracking the user‘s score.

In the Quiz component, we‘ll initialize state for the questions, current question index, score, and whether the quiz is over:

const [questions, setQuestions] = useState<QuestionState[]>([]);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [score, setScore] = useState(0);
const [isQuizOver, setIsQuizOver] = useState(false);

We‘ll use the useEffect hook to fetch the quiz questions when the component mounts and update the questions state:

useEffect(() => {
  fetchQuizQuestions(QUIZ_AMOUNT, QUIZ_DIFFICULTY)
    .then(questions => setQuestions(questions));
}, []);

The handleAnswer function will be called when the user selects an answer. It will update the score if the answer is correct, increment the current question index, and end the quiz if there are no more questions:

const handleAnswer = (answer: string) => {
  const isCorrect = answer === questions[currentQuestionIndex].correct_answer;
  if (isCorrect) {
    setScore(prevScore => prevScore + 1);
  }

  const nextQuestionIndex = currentQuestionIndex + 1;
  if (nextQuestionIndex < questions.length) {
    setCurrentQuestionIndex(nextQuestionIndex);
  } else {
    setIsQuizOver(true);
  }
};

Finally, we‘ll pass the current question and handleAnswer function down to the QuizQuestion component to display the question and handle user interactions.

Adding the Finishing Touches

With the core functionality in place, let‘s add some animations and transitions to spice up the UI.

We can use the framer-motion library to easily animate elements as they enter and exit the DOM. For example, to animate the quiz questions as they transition in and out:

import { motion } from ‘framer-motion‘;

const QuizQuestion = ({ question, onAnswer }) => (
  <motion.div
    key={question.question} 
    initial={{ opacity: 0, y: 50 }}
    animate={{ opacity: 1, y: 0 }}
    exit={{ opacity: 0, y: -50 }}
    transition={{ duration: 0.3 }}
  >
    {/* question content */}
  </motion.div>  
);

The motion.div component adds animation capabilities to a div. We specify initial, animate, and exit props to define how the element should animate when it enters, is present, and leaves the DOM. The transition prop lets us control the animation duration and easing.

We can also add more subtle transitions, like a background color change when the user hovers over an answer:

const QuizAnswer = styled.button`
  /* other styles */
  transition: background-color 0.3s;

  &:hover {
    background-color: ${props => props.theme.hoverColor};
  }
`;

By combining styled components with CSS transitions, we can create smooth, interactive UI elements that respond to user actions.

Conclusion and Next Steps

Congratulations, you now have a fully-functional quiz app built with React and TypeScript! We covered a lot of ground, from setting up the project and designing the components to fetching data, managing state, and adding animations.

But this is just the beginning. You can extend this basic app with all sorts of new features:

  • Letting the user choose the quiz category and difficulty
  • Adding a timer to limit the time per question
  • Implementing a leaderboard to track high scores
  • Customizing the styles and animations to match your preferred design

The best way to solidify your understanding is to try implementing these features yourself. Don‘t be afraid to experiment, break things, and learn from your mistakes. The more you practice, the more comfortable you‘ll become with React, TypeScript, and web development in general.

If you want to dive deeper into these topics, here are some resources to continue your learning journey:

Happy coding, and may your quizzes be engaging and delightful!

Similar Posts