Build a Countdown Timer for Writing
Tags: react, javascript
Published: 2020-11-06 19:10:12


[Nanowrimo](https://nanowrimo.org/) has started, but it’s easy to lose motivation. This countdown tool will put a figurative fire under our fingertips and hopefully inspire you to write a tiny draft to get started with that momentous writing project.

 ![Finished project with timer and writing form](https://dev-to-uploads.s3.amazonaws.com/i/0qyk5walmg2q7jy2x692.png)

## Getting Started

In my last article, we built a random plot generator to generate a random plot. But we didn’t actually create a place for us to write about that scenario. In order to challenge ourselves, we’re going to create a timed challenge component which will allow us to get our initial ideas on the page. 


I’ll be using the [Random Plot Generator component](![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0qyk5walmg2q7jy2x692.png)), so go ahead and read that article if you’d like to follow along. 


First, create a component that will render the form we’ll use to write on. 

This form will hold information in state, so we’ll make it a class component. 

```js
import React from ‘react’ 

class ChallengeForm extends React.Component{
	state={}

	render(){
		return(
                   <div>form goes here</div>
		)
	}

}

export default ChallengeForm 

```

In order to keep the styling consistent, I’ll use the styled components library. If you haven’t already, install the styled library. 

```js
npm install --save styled-components
```

I know I want a form, so I’ll build a Form with styled. 

```js
const Form = styled.form`
    width: 100%;
`
```


We’ll also need to build a text field for us to actually write something. 

Here’s the styling I used. Note that styled components should be OUTSIDE of the class declaration. 

```js
const TextField = styled.textarea`
    display: block;
    border: 1px solid lightgrey;
    border-radius: 2px;
    width: 750px;
    height: 500px;
    padding: 8px;
    margin: 8px;
`

```

Now, in the render method, render that form and textarea.

```js

<Form>
	<TextField/>
</Form>

``` 

Of course, we still can’t see the form. In our Random Plot Generator Component we need to import the Challenge Form component and render it. 

## Rendering the Form

We’ll work in our Random Plot Generator component for this next part. Refer to the [article](https://dev.to/kahawaiikailana/let-s-build-a-random-plot-generator-108) to get a feel for how it’s set up. 

```js
import ChallengeForm from './ChallengeForm'

[…]

render(){
	return(
		[…]
		<ChallengeForm/>
		)
	}
```

Here is our page so far. 

![Shows the text area underneath the randomly generated sentence](https://dev-to-uploads.s3.amazonaws.com/i/nlk9tjmcwng0tuyot1gc.png)

## Conditionally Render the Challenge

We can start writing ideas now, but there is a reason we called it the Challenge Form. To create the challenge aspect, we'll first need to conditionally render this form. 

We’ll change the render in our Random Plot generator to conditionally render the form. 

First, let’s add the form flag to our state. 

```js
class RandomPlotGenerator extends React.Component{
    constructor(props){
        super(props)
        this.state = {
         	[…]
            form: false
        }
    }
```

Then, in the render, write a ternary to render the form if it is true. 

```js
{this.state.form ? <ChallengeForm/>: null}
```

In order to make it true, we’ll need to write a button. Let’s add the button to the sentence that’s generated. 

Create a new styled button. 

```js
const Button = styled.button`
    margin: 8px; 
    padding; 8px;

    &:hover {
        color: blue;
        cursor: pointer;
      }
`
```

Then, render that button in the renderRandomPlot function. 

```js
    renderRandomPlot = () => {
        return this.state.sentences.map((sentence, idx) => 
             <Container key={idx}>
                 {sentence}
                 <Button onClick={this.onChallengeClick}>Challenge</Button>
             </Container>
        )
    }
```

Finally, change the state so that the form toggles between true and false. 

```js
  this.setState(prevState => ({
            sentences: [...prevState.sentences, sentence],
        }))
        }


```
Now we can show and hide the form at the click of a button. 

![Gif depicting the toggling of the form](https://dev-to-uploads.s3.amazonaws.com/i/52wsw2a9holqu3ofwyrp.gif)


Now that the form is conditionally rendered, let’s make a timer to count the time we have to write. 

## Building Countdown Functionality 

We’ll want to make a header to tell us how much time we have left. It would also be nice if, when we run out of time, the header blinks to let us know. 

### Styling the Countdown Headers

To do this, we need to import keyframes from the styled library. 

Do this in the Challenge Form component. 

```js
import styled, { keyframes } from 'styled-components'
```

Then, make a Title h3 styled component. 

```js
const Title = styled.h3`
    padding: 8px; 
`
```

We’ll also write a function for our component to blink. 

```js
function blink() {
    return keyframes`
      50% {
        opacity: 0;
      }
    `;
  }

const TimesUp = styled.text`
    color: red;
    animation: ${blink} 1s linear infinite;
`
```
Both this styled component and the function are outside of our Challenge Form class. 

### Keep Track of Time in State

Before rendering the title, we’ll add minutes and seconds to our state. 

```js
state = {
	minutes: 5, 
	seconds: 0
	}
```

We’ll use Set Interval to count down the seconds. 

I used [Charlie Russo’s Building a Simple Countdown Timer With React](https://medium.com/better-programming/building-a-simple-countdown-timer-with-react-4ca32763dda7) to build out this functionality. Check it out!

In the Component Did Mount lifecycle method, use this code to create the timer. 
```js
    componentDidMount() {
        this.myInterval = setInterval(() => {
          const { seconds, minutes } = this.state    
          if (seconds > 0) {
            this.setState(({ seconds }) => ({
              seconds: seconds - 1
            }))
          }
          if (seconds === 0) {
            if (minutes === 0) {
              clearInterval(this.myInterval)
            } else {
              this.setState(({ minutes }) => ({
                minutes: minutes - 1,
                seconds: 59
              }))
            }
          }
        }, 1000)
      }

    componentWillUnmount() {
        clearInterval(this.myInterval)
    }
```
### Conditionally Render the Countdown

Finally, render the countdown timer component. When the timer hits zero, our timer will blink to let us know time’s up. 

```js
   <Title>   
                    { minutes === 0 && seconds === 0
                        ? <TimesUp>Time's Up!</TimesUp>
                        : <h1>Time Remaining: {minutes}:{seconds < 10 ? `0${seconds}` : seconds}</h1>
                    }</Title>
```

And our countdown is complete!

![When the countdown hits zero, the title flashes](https://dev-to-uploads.s3.amazonaws.com/i/v20rkffk1u4mvozmk2kv.gif)

We can be mean and make it impossible to update the form after that, but that probably isn’t going to fly with many writers. Instead, let’s add an analysis tool that tells us how many words and characters we typed in that time period. 

## Building an Analysis Button 

We’ll create a simple button style for our analysis button. 

```js
const Button = styled.button`
    padding: 8px; 
    margin: 8px;
`
```

We’ll also render that button underneath our form. Let’s also attach an onClick event to it. 

```js
<Button onClick={this.analyze}>Analyze!</Button>
```

Analyze will conditionally render a word and character count, so we’ll need to add a flag to our state. 

```js
 analyze: false
```

Upon clicking the analyze button, we’ll set the state to true. 

```js
 analyze = () => {
        this.setState({
            analyze: true
        })
    }
```
In order to count the words, we’ll need to make them part of our state. 

```js
words: ''
```

Our count words function is a simple regex expression that counts words and returns only alpha-numeric characters. 

```js
  countWords = () => {
        let str = this.state.words
        const matches = str.match(/[\w\d\’\'-]+/gi);
        return matches ? matches.length : 0;
    }
    
```
Finally, we’ll tie the analyze function to the button and conditionally render the word and character count. 

```js
<Button onClick={this.analyze}>Analyze!</Button>
                    {this.state.analyze ? <p>{`You wrote ${this.countWords()} words and ${this.state.words.length} characters.`}</p> : null}
```

This will tell us how many words were written in our challenge. 

![Shows a word and character count](https://dev-to-uploads.s3.amazonaws.com/i/n7c363qsyy5az8cs73wi.png)


## Summary 

Great! We created a timer and text area to challenge ourselves to write a short story. We also created the ability to analyze the amount of words written in that time period. 

There are many ways this challenge can be expanded upon. What about creating multiple plot ideas and multiple challenge forms? Or creating a backend for saving our writing projects so we can come back to them later.