Building an Animated Counter with React and CSS
Tags: react, javascript, css, tutorial
Published: 2020-10-19 23:16:55

In this blog, I attempt to create my own version of an animated counter component that I keep finding all over the web.

I’ll show you how I went about it, but I would love feedback. Do you know a better way to do this?

Leave a comment or shoot me an email at `jason.melton2@gmail.com`.

![demo](https://dev-to-uploads.s3.amazonaws.com/i/6b7s6nbghg96lqjpzfse.gif)

> [the code on Github](https://github.com/cooljasonmelton/count-comp)

# Tutorial
### Table of Contents
* Preliminary Junk
* Count Component
* Increment Function
* Conclusion

### Preliminary Junk
I set up a `create-react-app`, deleted a bunch of default stuff, and a file structure like this:

![file structure](https://dev-to-uploads.s3.amazonaws.com/i/j9oozwdg3deumsels279.png)

I added some basic CSS to the App component — `height`, `width`, and `flex box` to center all its contents.

```css
.App {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
```
I also set up a JSON file containing the data that I will map into Count components.

```json
{
    "counts": [
        {
            "id": "001",
            "label": "A Really Big Number",
            "number": "900000",
            "duration": "2"
        },
        {
            "id": "002",
            "label": "Ice Cream Flavors",
            "number": "5321",
            "duration": "2"
        },
        {
            "id": "003",
            "label": "Customers Served",
            "number": "100",
            "duration": "2"
        },
        {
            "id": "004",
            "label": "Complaints",
            "number": "0",
            "duration": "2"
        }
    ]
}
```

### Count Component
The object of my Count component is to accept some data about how the count should run and render the animation.

First, I set up a basic component.

```react
import React from 'react';

//styling
import './Count.css';

const Count = props => {
  // label of counter
  // number to increment to
  // duration of count in seconds
  const {label, number, duration } = props.data

  return (
    <div className="Count">
      <h3>
        <i>{label}: {number}</i>
      </h3>
    </div>
  );
}

export default Count;

```

Count gets props of a data item from data.json. I destructured the `label`, `number`, and `duration` from the props.

Using JSX, I return the `label` and `number` as a header. 

Later, I will change `number` so that it animates, but for now I can style the hard-coded version of what I’m building.
```css
.Count {
    padding: 2rem;
    margin: 1rem;

    border-radius: 2em;
    box-shadow: 1px 2px 2px #0D3B66;
    background-color: #FAF0CA;

    display: flex;
    align-items: center;
    justify-content: center;

    color: #0D3B66;
}
```

### Increment Function
I set up a function that increments from 0 to the desired number in these three steps:

1) Set up a `useState` hook that saves our display number and, when updated, will trigger a render of the component.

The hook looks like this:
```react
  // number displayed by component
  const [count, setCount] = useState("0");
```

I update the JSX to display `count` instead of `number`.

```react
  return (
    <div className="Count">
      <h3>
        <i>{label}: {count}</i>
      </h3>
    </div>
  );
```

2) Set up a `useEffect` hook that calculates the count and increment time.

`useEffect()` takes an anonymous function that will handle the count. I create variables `start` and `end`. `start` is set to 0.

Initially, I used `number` as my end. However, for large numbers, this would take all night. Instead, I only increment the first three digits of the number and paste the the rest of back before updating the count. 

I calculate the rate of each increment by dividing the duration (seconds) by the number of increments I plan on doing and multiply by 1000 to convert to milliseconds.
Image for post

```react
  useEffect(() => {
    let start = 0;
    // first three numbers from props
    const end = parseInt(number.substring(0,3))
    // if zero, return
    if (start === end) return;

    // find duration per increment
    let totalMilSecDur = parseInt(duration);
    let incrementTime = (totalMilSecDur / end) * 1000;

    // dependency array
  }, [number, duration]);
```
> I was hoping to speed up the interval to make up for how long it would take to increment large numbers, but `setInterval()` has a minimum duration of 10 milliseconds. Any number less than 10 will reset back to 10.

3) In that same useEffect hook, I employ `setInterval()` to increment the count with side effect of re-rendering the component.

I add one to start and call `setCount()` to update my `useState` hook. I convert `start` to a string and, if it’s a large number, I concat the rest of the number that I previously chopped off.

```react
    // timer increments start counter 
    // then updates count
    // ends if start reaches end
    let timer = setInterval(() => {
      start += 1;
      setCount(String(start) + number.substring(3))
      if (start === end) clearInterval(timer)       
    }, incrementTime);
```

The entire component will now look like this:
```react
import React, { useEffect, useState } from 'react';

//styling
import './Count.css';

const Count = props => {
  // label of counter
  // number to increment to
  // duration of count in seconds
  const {label, number, duration } = props.data

  // number displayed by component
  const [count, setCount] = useState("0")

  useEffect(() => {
    let start = 0;
    // first three numbers from props
    const end = parseInt(number.substring(0,3))
    // if zero, return
    if (start === end) return;

    // find duration per increment
    let totalMilSecDur = parseInt(duration);
    let incrementTime = (totalMilSecDur / end) * 1000;

    // timer increments start counter 
    // then updates count
    // ends if start reaches end
    let timer = setInterval(() => {
      start += 1;
      setCount(String(start) + number.substring(3))
      if (start === end) clearInterval(timer)       
    }, incrementTime);

    // dependency array
  }, [number, duration]);

  return (
    <div className="Count">
      <h3>
        <i>{label}: {count}</i>
      </h3>
    </div>
  );
}

export default Count;
```

### Conclusion
I read through several articles about this sort of animation and combined their ideas with my instinct to make this abstract reusable component.

I am not sure what I came up with is the best method. For example `setInterval` had limitations I didn’t foresee. I would love some feedback. Feel free to comment or shoot me an email at `jason.melton2@gmail.com`.

Best, Jason