Building a WPM Typing Test With React Hooks
During my sabbatical between jobs a hacker news post caught my eye that someone built a novel typing website to practice typing while reading one of your favorite books. I thought this would be an interesting quick mini React project to replicate this website with music lyrics. During the development process I found that there were some good Reactive principles that can be taught from it, so now here I am doing my first medium article to teach anyone interested…
This article will teach you how to build the core building blocks of the below gif:
Step 1 — Start with a bootstrap react project:
npx create-react-app my-app
cd my-app# Download material-ui library
npm install @material-ui/core
Step 2 — Create a key press hook
Create a hooks folder and then create a useKeyPress.js file for our key press hook.
For a refresher on custom hooks visit the source here.
- Create custom hook called useKeyPress that receives a callback to be called when a key is pressed down.
- Use the state hook to keep track of the key that is pressed
- Use the effect hook that doesn’t re-render by passing it an empty dependency array as it’s second parameter
- Create a key down handler that’ll control when to call the callback function with the pressed key. The callback ensures that a single character was pressed and not a special character such as “CTRL”, “ALT”, etc. It also prevents space bar from automatically scrolling down the page
- Create a key up handler to reset key pressed state
- Create listeners with their respective handlers
- Once hook is unmounted remove listeners on keys
Step 3 — Use custom key press hook in the application
Open up App.js that is created for you when using create-react-app. Delete everything that it returns and let’s start from scratch.
Might be a lot to digest from this snippet, but I’ll try to break down for anyone that is confused. Most of our programming logic is in the TypingText component which utilizes the hook that we created in the last step. We pass a callback to our keyPress hook that controls our state management by checking if a backspace was pressed, and if so delete the last character in our chrs typed array. If any other character was typed then append it to our chrs typed state array. If you try mutating the state here and setting it as the new state you will run into errors.
UseEffect hook is used here so that we only calculate the characters the user needs to type once on mount of the component. Also should note that we are using the useRef hook for the timer because we don’t want to recreate the timer on every render and also don’t want to store it in a state because changing the timer would fire off a useless re-render of the component. We then write a couple conditionals to keep track of when to start and stop the timer, which is used to keep track of the client’s words per a minute.
The render function then displays the WPM and accuracy that we calculated using our state variables. We also break up each line of the text into separate box containers that render each character in line. Take note of the unique id we pass as a prop to each Character component as that will be important in efficiently rendering the component.
Step 4 — Memoization of the character component
Memoizing the Character component was necessary for large typing scripts because with out doing so every single character is re-rendered for each key that is typed. As you can imagine that doesn’t scale when you have thousands of characters on a page.
For React memo documentation go here. The memo function accepts a component, which is passed as a functional component in this example, and the second optional parameter is a function that is called to see if this component should be rendered.
When creating this typing test/game you only need to change the color of the character that was just typed, the next character to display to the user what the next character they need to type, and also may have to remove any styling to the following character in case a backspace was pressed. Given that this check to render is inexpensive and rendering is an expensive operation we only do constant time of expensive work when a key is pressed (O(3) time complexity). You can go ahead and experiment with the response time without memoization and increasing the text size, and see how that effects the render time for each key pressed.
PS. if anyone is curious on what I’m returning in the functional component. I created separate styled components for each character depending if it’s a neutral state, incorrect state, and next character state. You can style these components however you like.
If you enjoyed this article, or if you have any recommendations on what I should adjust or write on in the future let me know in the comments below! Final product of this project is located here.