<!DOCTYPE html>
MonkeyType React Clone: Building a Typing Test App
<br> body {<br> font-family: sans-serif;<br> margin: 20px;<br> }<br> h1, h2, h3 {<br> margin-top: 30px;<br> }<br> pre {<br> background-color: #f0f0f0;<br> padding: 10px;<br> border-radius: 5px;<br> overflow-x: auto;<br> }<br> .code-block {<br> display: flex;<br> align-items: center;<br> margin-bottom: 10px;<br> }<br> .code-block img {<br> width: 30px;<br> margin-right: 10px;<br> }<br> img {<br> max-width: 100%;<br> height: auto;<br> margin-bottom: 10px;<br> }<br>
MonkeyType React Clone: Building a Typing Test App
MonkeyType is a popular online typing test website that allows users to test their typing speed and accuracy. It's a fun and engaging way to practice typing skills and track progress. In this article, we'll guide you through the process of creating a MonkeyType clone using React, a powerful JavaScript library for building user interfaces.
This project will involve working with:
-
React:
A JavaScript library for building user interfaces. -
React Hooks:
Modern features in React for managing component state and side effects. -
State Management (Optional):
Libraries like Redux or Zustand for handling complex application state. -
CSS:
Styling the application. -
APIs (Optional):
For fetching text passages, storing user data, or integrating with other services.
- Project Setup
We'll start by creating a new React project using Create React App. This will provide us with a pre-configured project structure and necessary dependencies.
npx create-react-app monkeytype-clone
cd monkeytype-clone
Once the project is set up, we can run the development server:
npm start
This will open the application in your browser, usually at http://localhost:3000
.
We'll break down our MonkeyType clone into several components:
- App: The main component that renders the other components.
- TypingArea: Displays the text to be typed and the user's input.
- Timer: Tracks and displays the elapsed time.
- Result: Shows the user's typing speed and accuracy after they finish typing.
- TextPicker: (Optional) Allows the user to select different text passages to type.
The TypingArea
component is the heart of our application. It handles the following:
- Displaying the text passage.
- Taking user input.
- Highlighting incorrect characters.
- Calculating words per minute (WPM).
Here's a basic implementation of the TypingArea
component:
import React, { useState, useEffect, useRef } from 'react';
function TypingArea() {
const [text, setText] = useState('');
const [userInput, setUserInput] = useState('');
const [startTime, setStartTime] = useState(null);
const [endTime, setEndTime] = useState(null);
const [wpm, setWpm] = useState(0);
const [accuracy, setAccuracy] = useState(0);
const inputRef = useRef(null);
useEffect(() => {
fetch('https://api.quotable.io/random')
.then(response => response.json())
.then(data => setText(data.content));
}, []);
useEffect(() => {
if (startTime && endTime) {
const timeTaken = endTime - startTime;
const wordsTyped = userInput.trim().split(/\s+/).length;
const calculatedWpm = Math.round((wordsTyped / timeTaken) * 60);
setWpm(calculatedWpm);
setAccuracy(calculateAccuracy(text, userInput));
}
}, [startTime, endTime, userInput, text]);
const calculateAccuracy = (originalText, typedText) => {
let correctCharacters = 0;
const minLength = Math.min(originalText.length, typedText.length);
for (let i = 0; i < minLength; i++) {
if (originalText[i] === typedText[i]) {
correctCharacters++;
}
}
return Math.round((correctCharacters / originalText.length) * 100);
};
const handleInputChange = (e) => {
setUserInput(e.target.value);
if (!startTime) {
setStartTime(Date.now());
}
};
const handleKeyDown = (e) => {
if (e.key === ' ') {
setEndTime(Date.now());
}
};
return (
<div>
<h1>
Typing Test
</h1>
<p>
{text}
</p>
<textarea onchange="{handleInputChange}" onkeydown="{handleKeyDown}" placeholder="Start typing..." ref="{inputRef}" value="{userInput}"></textarea>
<p>
WPM: {wpm}
</p>
<p>
Accuracy: {accuracy}%
</p>
</div>
);
}
export default TypingArea;
In this code:
- We use state variables to manage the text passage, user input, start/end times, WPM, and accuracy.
-
The
useEffect
hook fetches a random quote from a quote API to display as the typing text. -
Another
useEffect
hook calculates the WPM and accuracy after the user finishes typing. -
The
handleInputChange
function updates theuserInput
state as the user types. -
The
handleKeyDown
function records the end time when the user presses the spacebar. -
We use a
textarea
element to display the typing area.
You can customize the API used to fetch the text and implement different ways to display the results.
- Timer Component
The Timer
component handles displaying the elapsed time since the user started typing.
import React, { useState, useEffect } from 'react';
function Timer({ startTime, endTime }) {
const [elapsedTime, setElapsedTime] = useState(0);
useEffect(() => {
let intervalId;
if (startTime) {
intervalId = setInterval(() => {
setElapsedTime(Date.now() - startTime);
}, 1000);
}
return () => clearInterval(intervalId);
}, [startTime]);
const formatTime = (ms) => {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
};
return (
<div>
{startTime && endTime ? (
<p>
Time: {formatTime(endTime - startTime)}
</p>
) : startTime ? (
<p>
Time: {formatTime(elapsedTime)}
</p>
) : (
<p>
Time: 00:00
</p>
)}
</div>
);
}
export default Timer;
In this code:
-
We use a
useEffect
hook to update theelapsedTime
state every second usingsetInterval
. -
The
formatTime
function converts milliseconds to minutes and seconds. - We display the time based on whether the user has started and finished typing.
- Result Component
The Result
component displays the user's WPM and accuracy after they finish typing.
import React from 'react';
function Result({ wpm, accuracy }) {
return (
<div>
{wpm && accuracy && (
<>
<h2>
Results
</h2>
<p>
WPM: {wpm}
</p>
<p>
Accuracy: {accuracy}%
</p>
)}
</div>
);
}
export default Result;
- App Component
The App
component is the main component that renders all the other components.
import React from 'react';
import TypingArea from './TypingArea';
import Timer from './Timer';
import Result from './Result';
function App() {
return (
<div>
<typingarea>
</typingarea>
<timer>
</timer>
<result>
</result>
</div>
);
}
export default App;
- Styling
You can style your MonkeyType clone using CSS. Here's an example of basic styling:
body {
font-family: sans-serif;
margin: 20px;
}
.typing-area {
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
}
textarea {
width: 100%;
height: 150px;
resize: none;
padding: 10px;
font-size: 16px;
}
Add this CSS to a file named src/App.css
and import it into App.js
:
import './App.css';
You can use CSS frameworks like Bootstrap or Tailwind CSS for more advanced styling.
- TextPicker Component (Optional)
To give users more control over the text they type, you can create a TextPicker
component that allows them to choose from different text passages. This component could fetch text from a database or use a pre-defined list of texts.
For larger and more complex applications, you might want to use a state management library like Redux or Zustand to manage the application state more efficiently.
Once you're satisfied with your MonkeyType clone, you can deploy it to a hosting service like Netlify or Vercel. These services offer easy deployment options for React applications.
Conclusion
This article has shown you how to create a MonkeyType clone using React. By building this application, you've gained experience working with:
- React components and state management.
- Fetching data from APIs.
- Handling user input and events.
- Styling your application with CSS.
You can extend this project by adding features like:
- User accounts to save typing history.
- Leaderboards to compare scores with other users.
- Custom text input options.
- Different typing modes like timed or accuracy focused.
- Game-like elements to make typing more engaging.
Remember to keep your code clean, well-documented, and maintainable. Happy coding!