1 minute read

I built a custom hook that quickly changes a number until it reaches a target value.

code

import { useEffect, useState } from 'react';

type useRollingNumberProps = {
  target: number;
  min?: number;
  max?: number;
};

const useRollingNumber = ({
  target,
  min = 0,
  max = 100,
}: useRollingNumberProps) => {
  const [animatedNumber, setAnimatedNumber] = useState(0); // the number to display on screen

  useEffect(() => {
    if (animatedNumber === 0) return setAnimatedNumber(target); // set the initial value
    if (animatedNumber === target) return; // change complete

    // change the number only at a rate visible to the human eye
    requestAnimationFrame(() => {
      const increment = target > animatedNumber ? 1 : -1; // check whether to increase or decrease the number

      setAnimatedNumber((prev) => {
        if (increment) return prev + increment > max ? max : prev + increment; // when increasing, only go up to max
        return prev + increment <= min ? min : prev + increment; // when decreasing, only go down to min
      });
    });
  }, [target, min, max, animatedNumber]);

  return {
    animatedNumber,
  };
};

export default useRollingNumber;

usage

const Info = () => {
  const { experiencePercent } = useMainCharacterStore(); // // increase or decrease by the experiencePercent value
  const { animatedNumber } = useRollingNumber({ target: experiencePercent, min: 0, max: 100 }); // the target is max 0, max 100

  return (
    <div className='info'>
      <div className='gauge'>
        <span className='graph' style={{ width: `${animatedNumber}%` }} />
        <strong className='percent'>{animatedNumber}%</strong>
      </div>
    </div>
  );
};

export default Info;

20240326

Tags:

Categories:

Updated:

Leave a comment