I like to start my year off by setting a reading goal. This relatively quick project will calculate how long it’ll take to complete your reading challenge based on words read per minute and the average length of a book.
This project is perfect for those just starting React, or those who want to refresh their skills after taking a break.
Getting Started
Create a new project by using the Create New React App command.
$ npx create-react-app reading-challenge
After your project has been created, create a components folder in src and add two new files: Form.js and ReadingLog.js.
ReadingLog will be responsible for some of the styling and some of the content, but most of the meat of the project will be in Form.
You can also feel free to delete the files you don’t need.
Our display file
I’ve kept the display file super short. It displays a title and the form component.
import React from 'react';
import Form from './Form';
function Readlog(){
return(
<div>
<h1>How long will my TBR take?</h1>
<Form/>
</div>
)
}
export default Readlog;
This a good file to add styling to as you can wrap the form and other elements in it.
Figuring out the form
My first idea was to differentiate books based on children’s fiction, young adult fiction, and general literature. However, that kind of information can be hard to parse, even if you have a CSV file handy.
Instead, I went for an MVP version of that idea.
I researched the average length of a novel, which is 90,000 words. With this information, I only needed to figure out my WPM or words per minute. I used this speed reading test to calculate my speed.
With that in mind, the formula would look something like this:
minutes = Number of books x 90000 / words per minute
For the above formula, what kind of information would a user need to disclose?
- number of TBR books
- reading speed
This information can be held in state.
class Form extends React.Component{
constructor(){
super()
this.state={
books: 0,
wordsPerMinute: 0
}
}
[…]
}
Now, we’ll need a form for the user to put this information into. Since both of these variables are numbers, I’ve used type=“number” on the input form.
render(){
return(
<div>
<form>
<label>Number of books on your TBR</label>
<input
placeHolder="enter the number of books"
type="number"
name="book"
value={this.state.books}
onChange={ (e) => this.handleChange(e) }
/>
<label>Reading Speed (wpm)</label>
<input
type="number"
name="wordsPerMinute"
value={this.state.wordsPerMinute}
onChange={ (e) => this.handleChange(e) }
/>
</form>
</div>
)
}
You’ll notice that there’s an onChange function being referenced here, so it’s a good Idea to write one.
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
Fun fact: the brackets around [e.target.name] tells react that this code refers to a dynamic key name.
So we have a form, and we can change the state using the form.
Now, we need to actually calculate the number of minutes, hours, and days it’ll take to read all those books based on the user’s reading speed.
Time conversion
We have the reader’s wpm represented by this.state.wordsPerMinute, and we have their TBRs based on this.state.books.
let wpm = this.state.wordsPerMinute
let books = this.state.books
Glancing at our formula again, in order to calculate the minutes, we’ll need to multiply the number of books by 90000 and divide by the wpm. I’ve also used Math.floor so we get nice, whole numbers. This will give us the minutes it’ll take to read all those books (give or take).
let minutes = Math.floor((books * 90000)/wpm) || 0
Note that || 0 will give the user a 0 instead of NaN value if they enter 0 into the input form.
With this information, we only need to do a couple more time conversions to calculate the days and hours.
let hours = Math.floor(minutes/60) || 0
let days = (hours/24).toFixed(1) || 0
Then, with those times, we can add a bit of dialogue to the state, that will be dynamically rendered when the user clicks a button. To do this we’ll need to write it into our function.
this.setState({
time:`It'll take about ${minutes} minutes to get through your TBR list if you read continuously. That translates to ${hours} hours or about ${days} days.`
})
And we’ll also need to include it in our state:
class Form extends React.Component{
constructor(){
super()
this.state={
books: 0,
wordsPerMinute: 0,
time:’ ‘
}
}
The whole function looks like this:
calculateTime = () => {
let wpm = this.state.wordsPerMinute
let books = this.state.books
let minutes = Math.floor((books * 90000)/wpm) || 0
let hours = Math.floor(minutes/60) || 0
let days = (hours/24).toFixed(1) || 0
this.setState({
time:`It'll take about ${minutes} minutes to get through your TBR list if you read continuously. That translates to ${hours} hours or about ${days} days.`
})
}
You can tie the form to an onSubmit function…
<form onSubmit={(e) => this.handleSubmit(e)}>
[…]
<input type=“submit”/>
…and write out the submit function.
handleSubmit = (e) => {
e.preventDefault()
this.calculateTime()
}
Keep in mind that e.preventDefault() will prevent the form from doing an annoying page refresh.
Now, all that’s left to do, is add the dialogue from state (we’ll use a ternerary to conditionally render it after the user clicks the button).
<p>{this.state.time ? this.state.time : null}</p>
Altogether, our file will look like this
import React from 'react';
class Form extends React.Component{
constructor(){
super()
this.state={
books: 0,
wordsPerMinute: 0,
time: ''
}
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
calculateTime = () => {
let wpm = this.state.wordsPerMinute
let books = this.state.books
let minutes = Math.floor((books * 90000)/wpm) || 0
let hours = Math.floor(minutes/60) || 0
let days = (hours/24).toFixed(1) || 0
this.setState({
time:`It'll take about ${minutes} minutes to get through your TBR list if you read continuously. That translates to ${hours} hours or about ${days} days.`
})
}
handleSubmit = (e) => {
e.preventDefault()
this.calculateTime()
}
render(){
return(
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label>Number of books on your TBR</label>
<input
placeHolder="enter the number of books"
type="number"
name="book"
value={this.state.books}
onChange={ (e) => this.handleChange(e) }
/>
<label>Reading Speed (wpm)</label>
<input
type="number"
name="wordsPerMinute"
value={this.state.wordsPerMinute}
onChange={ (e) => this.handleChange(e) }
/>
<input type="submit"/>
</form>
<p>{this.state.time ? this.state.time : null}</p>
</div>
)
}
}
export default Form
Ideas for expansion
This works great, but I can’t help but feel that the number it gives could be more accurate. This stackoverflow discussion about book word counts seems like a pretty good place to start.
Summary
Even though it’s going on 9 months since I first picked up React, there’s still so much to learn. Quick projects are a good way to practice and improve on skills without feeling overwhelmed. It was also useful to review syntax like brackets around event.target.name.
Hope your reading challenges go well!
Top comments (0)