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.
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:
I added some basic CSS to the App component — height, width, and flex box to center all its contents.
.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.
{
    "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.
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.
.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:
  // number displayed by component
  const [count, setCount] = useState("0");
I update the JSX to display count instead of number.
  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
  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.
    // 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:
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
              

    
Top comments (1)
thanks for reading my dude