Build a Simple Next.js Interactive Typing Speed Test

Written by

in

In today’s fast-paced digital world, typing speed is more crucial than ever. Whether you’re a student, a professional, or simply someone who enjoys communicating online, the ability to type quickly and accurately can significantly boost your productivity and efficiency. This tutorial will guide you through building a simple, interactive typing speed test application using Next.js, a powerful React framework that makes web development a breeze. We’ll cover everything from setting up your project to implementing the core features, all while keeping the code clean, understandable, and beginner-friendly.

Why Build a Typing Speed Test?

Creating a typing speed test is an excellent project for several reasons:

  • Practical Application: It’s a useful tool that you can use daily to track your progress and improve your typing skills.
  • Learning Opportunity: It allows you to practice fundamental web development concepts such as state management, event handling, and DOM manipulation.
  • Fun and Engaging: It’s an interactive project that offers immediate feedback, making the learning process enjoyable.
  • SEO Benefits: A well-crafted typing test can attract users searching for such a tool, boosting your website’s traffic.

By the end of this tutorial, you’ll have a fully functional typing speed test that you can customize and expand upon. Let’s get started!

Prerequisites

Before we dive in, ensure you have the following installed on your machine:

  • Node.js and npm (or yarn): You’ll need Node.js and npm (Node Package Manager) or yarn to manage your project’s dependencies. You can download them from nodejs.org.
  • A Code Editor: A code editor like Visual Studio Code, Sublime Text, or Atom will be helpful for writing and editing your code.
  • Basic Knowledge of HTML, CSS, and JavaScript: Familiarity with these web technologies is essential for understanding the code and making modifications.

Setting Up the Next.js Project

Let’s create a new Next.js project. Open your terminal and run the following command:

npx create-next-app typing-speed-test

This command will set up a new Next.js project named “typing-speed-test”. Navigate into the project directory:

cd typing-speed-test

Now, start the development server:

npm run dev

This will start the development server, usually on http://localhost:3000. Open this address in your browser to see the default Next.js welcome page. With the project setup, we can start building the typing speed test.

Project Structure

Before we start coding, let’s understand the basic file structure of a Next.js project. We’ll be primarily working with the following files:

  • pages/index.js: This file will contain the main content of our typing speed test application.
  • styles/globals.css: This file will hold global styles for our application.

Building the User Interface (UI)

Let’s start by designing the UI for our typing speed test. Open pages/index.js and replace the existing code with the following:

import { useState, useEffect } from 'react';

export default function Home() {
  return (
    <div className="container">
      <h1>Typing Speed Test</h1>
      <div className="test-area">
        <p className="quote">Loading...</p>
        <input type="text" className="input-field" placeholder="Start typing..." />
      </div>
      <div className="stats">
        <p>WPM: <span id="wpm">0</span></p>
        <p>Accuracy: <span id="accuracy">100%</span></p>
        <p>Time: <span id="time">0s</span></p>
      </div>
    </div>
  );
}

This code sets up the basic layout of our typing test. We have a heading, a test area containing the quote and the input field, and a stats section to display the words per minute (WPM), accuracy, and time. We’ve also added some placeholder text for the quote and stats. Let’s add some basic styling to make it look better. Open styles/globals.css and add the following CSS:

body {
  font-family: sans-serif;
  margin: 0;
  padding: 0;
  background-color: #f0f0f0;
}

.container {
  width: 80%;
  margin: 50px auto;
  background-color: #fff;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  text-align: center;
}

.test-area {
  margin-bottom: 20px;
}

.quote {
  font-size: 1.2rem;
  margin-bottom: 10px;
  word-wrap: break-word; /* Prevents long words from overflowing */
}

.input-field {
  width: 100%;
  padding: 10px;
  font-size: 1rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box; /* Ensures padding doesn't affect the width */
}

.stats {
  display: flex;
  justify-content: space-around;
  font-size: 1.1rem;
}

This CSS provides basic styling for the container, headings, test area, quote, input field, and stats section. Refresh your browser, and you should see a basic layout of our typing speed test.

Fetching a Quote

Next, we need a quote for the user to type. We’ll fetch a random quote from a public API. First, we’ll create a state variable to hold the quote. Modify pages/index.js as follows:

import { useState, useEffect } from 'react';

export default function Home() {
  const [quote, setQuote] = useState("Loading...");

  useEffect(() => {
    async function fetchQuote() {
      const response = await fetch("https://api.quotable.io/random");
      const data = await response.json();
      setQuote(data.content);
    }

    fetchQuote();
  }, []);

  return (
    <div className="container">
      <h1>Typing Speed Test</h1>
      <div className="test-area">
        <p className="quote">{quote}</p>
        <input type="text" className="input-field" placeholder="Start typing..." />
      </div>
      <div className="stats">
        <p>WPM: <span id="wpm">0</span></p>
        <p>Accuracy: <span id="accuracy">100%</span></p>
        <p>Time: <span id="time">0s</span></p>
      </div>
    </div>
  );
}

Here, we added the following:

  • Imported useState and useEffect: These are React hooks that allow us to manage state and perform side effects.
  • Created a quote state variable: This variable holds the quote to be displayed. We initialize it to “Loading…”.
  • Used useEffect to fetch the quote: The useEffect hook runs after the component renders. Inside it, we define an async function fetchQuote that fetches a random quote from the Quotable API (https://api.quotable.io/random).
  • Updated the UI to display the quote: We replace the placeholder text “Loading…” with the quote state variable.

Now, when you refresh the page, a random quote from the API will be displayed.

Adding Typing Functionality

Now, let’s add the functionality to capture the user’s input and compare it to the quote. We’ll need to do the following:

  • Capture User Input: Store the user’s input in a state variable.
  • Compare Input to Quote: Compare the user’s input to the displayed quote.
  • Highlight Correct and Incorrect Characters: Provide visual feedback to the user by highlighting correct and incorrect characters.
  • Calculate WPM and Accuracy: Calculate and display the user’s WPM and accuracy.
  • Start and Stop the Timer: Implement a timer to track the typing time.

Modify your pages/index.js file to include these features. Here’s the updated code:

import { useState, useEffect, useRef } from 'react';

export default function Home() {
  const [quote, setQuote] = useState("Loading...");
  const [userInput, setUserInput] = useState("");
  const [startTime, setStartTime] = useState(null);
  const [endTime, setEndTime] = useState(null);
  const [wpm, setWpm] = useState(0);
  const [accuracy, setAccuracy] = useState(100);
  const [charIndex, setCharIndex] = useState(0);
  const [correctChars, setCorrectChars] = useState(0);
  const inputRef = useRef(null);

  useEffect(() => {
    async function fetchQuote() {
      const response = await fetch("https://api.quotable.io/random");
      const data = await response.json();
      setQuote(data.content);
      setCharIndex(0);
      setCorrectChars(0);
      setUserInput('');
      setStartTime(null);
      setEndTime(null);
      setWpm(0);
      setAccuracy(100);
    }

    fetchQuote();
  }, []);

  useEffect(() => {
    if (startTime && endTime) {
      calculateResults();
    }
  }, [endTime]);

  const handleInputChange = (e) => {
    const inputText = e.target.value;
    setUserInput(inputText);

    if (!startTime) {
      setStartTime(new Date());
    }

    // Calculate accuracy and highlight characters (basic implementation)
    let correct = 0;
    let charIndexLocal = 0;
    for (let i = 0; i < inputText.length; i++) {
      if (inputText[i] === quote[i]) {
        correct++;
        charIndexLocal = i + 1;
      }
    }
    setCorrectChars(correct);
    setCharIndex(charIndexLocal);

    if (inputText.length === quote.length) {
      setEndTime(new Date());
    }
  };

  const calculateResults = () => {
    const timeInSeconds = (endTime - startTime) / 1000;
    const wordsTyped = userInput.split(' ').length;
    const correctWords = userInput.split(' ').filter((word, index) => word === quote.split(' ')[index]).length;
    const accuracyCalc = Math.round((correctChars / quote.length) * 100);

    const wpmCalc = Math.round((wordsTyped / timeInSeconds) * 60);
    setWpm(wpmCalc);
    setAccuracy(accuracyCalc);
  };

  const resetTest = () => {
    async function fetchNewQuote() {
      const response = await fetch("https://api.quotable.io/random");
      const data = await response.json();
      setQuote(data.content);
      setUserInput('');
      setStartTime(null);
      setEndTime(null);
      setWpm(0);
      setAccuracy(100);
      setCharIndex(0);
      setCorrectChars(0);
    }
    fetchNewQuote();
    inputRef.current.focus();
  };

  return (
    <div className="container">
      <h1>Typing Speed Test</h1>
      <div className="test-area">
        <p className="quote">
          {quote.split('').map((char, index) => {
            let className = '';
            if (index < charIndex) {
              className = userInput[index] === char ? 'correct' : 'incorrect';
            }
            return (
              <span key={index} className={className}>
                {char}
              </span>
            );
          })}
        </p>
        <input
          type="text"
          className="input-field"
          placeholder="Start typing..."
          value={userInput}
          onChange={handleInputChange}
          ref={inputRef}
        />
      </div>
      <div className="stats">
        <p>WPM: <span id="wpm">{wpm}</span></p>
        <p>Accuracy: <span id="accuracy">{accuracy}%</span></p>
        <p>Time: <span id="time">{startTime && endTime ? ((endTime - startTime) / 1000).toFixed(0) + 's' : '0s'}</span></p>
      </div>
        <button onClick={resetTest}>Reset</button>
    </div>
  );
}

Here’s a breakdown of the changes:

  • Added State Variables: We added state variables for userInput (to store the user’s input), startTime and endTime (to track the time), wpm (words per minute), and accuracy (percentage of correct characters). We’ve also added charIndex to track the current character index the user is typing, correctChars to track correct characters, and inputRef to focus on the input field.
  • handleInputChange Function: This function is called every time the user types in the input field. It updates the userInput state, starts the timer when the user starts typing, and calculates the accuracy.
  • Character Highlighting: The quote is now displayed as a series of <span> elements, each representing a character. We use the charIndex and compare the typed characters to the quote to apply the ‘correct’ or ‘incorrect’ CSS classes.
  • calculateResults Function: This function calculates the WPM and accuracy based on the user’s input, the quote, the start time, and the end time.
  • Displaying Results: The WPM, accuracy, and time are displayed in the stats section.
  • Reset Functionality: Added a reset function to fetch a new quote, reset the input field and reset the stats. Also added a button to call the reset function

Now, add some CSS to highlight correct and incorrect characters. Update the styles/globals.css file:


.correct {
  color: green;
}

.incorrect {
  color: red;
}

With these changes, your typing speed test should be functional. As the user types, the characters will be highlighted as correct or incorrect, and the WPM and accuracy will be displayed.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to fix them:

  • Incorrect Character Highlighting: The character highlighting might not work correctly if the logic for comparing the user’s input to the quote is flawed. Double-check your code to ensure you’re comparing the correct characters at the correct indices.
  • Timer Issues: The timer might not start or stop correctly. Ensure that the startTime is set when the user starts typing and the endTime is set when the user finishes typing. Also, make sure that the timer is only calculating the time between start and end.
  • Accuracy Calculation Errors: The accuracy calculation might be off. Ensure that you are dividing the number of correct characters by the total number of characters in the quote and multiplying by 100.
  • WPM Calculation Errors: The WPM calculation might not be accurate. Ensure that you are dividing the number of words typed by the time in minutes (time in seconds / 60) and multiplying by 60.
  • API Errors: If the API is down, the app will not work. Make sure to implement proper error handling.

Enhancements and Further Development

Here are some ideas to enhance your typing speed test:

  • Add a Difficulty Level: Allow users to select different difficulty levels (e.g., easy, medium, hard) that affect the length and complexity of the quotes.
  • Implement a Scoreboard: Store the user’s scores and display a leaderboard. You could use local storage or a database for this.
  • Add a Custom Quote Input: Allow users to enter their own quotes to practice typing.
  • Improve the UI/UX: Enhance the visual design and user experience of the application. Consider using a UI library like Tailwind CSS or Material UI.
  • Add Sound Effects: Play sound effects when the user types a correct or incorrect character.
  • Implement a Mobile-Friendly Design: Ensure the application is responsive and works well on mobile devices.
  • Error Handling: Implement error handling for API requests and user input validation.

Key Takeaways

  • You’ve learned how to build a basic typing speed test application using Next.js.
  • You’ve gained experience with state management, event handling, and DOM manipulation in React.
  • You’ve learned how to fetch data from an external API.
  • You’ve implemented a timer and calculated WPM and accuracy.
  • You’ve gained insights into potential improvements and enhancements.

FAQ

  1. How can I deploy this application?

    You can deploy your Next.js application to platforms like Vercel, Netlify, or AWS. Vercel is the easiest option as it’s designed for Next.js applications and provides automatic deployments.

  2. How can I make the application responsive?

    You can use CSS media queries or a CSS framework like Tailwind CSS or Bootstrap to make your application responsive. This will ensure that the application looks and functions correctly on different screen sizes.

  3. How can I store user scores?

    You can store user scores using local storage in the browser, or by integrating with a backend database. Local storage is suitable for simple applications, while a database is recommended for more complex applications with user accounts and leaderboards.

  4. How can I improve the accuracy calculation?

    The accuracy calculation can be improved by considering spaces, punctuation, and other special characters. You can also implement a more sophisticated character comparison algorithm.

Building this typing speed test is a fantastic way to learn Next.js and React. You’ve created a functional and engaging application from scratch, gaining valuable experience with web development concepts. Remember that the journey of learning never truly ends; there are always new features to implement, designs to refine, and optimizations to make. Continue exploring, experimenting, and refining your skills to build even more amazing web applications in the future. Embrace the process of continuous learning, and you’ll be well on your way to becoming a proficient web developer. The knowledge and skills you have gained will serve as a solid foundation for your future projects, opening doors to endless possibilities in the world of web development. Keep coding, keep creating, and keep improving.