Skip to main content

useCounter hook with Framer Motion

useCounter Hook

import * as React from "react";
import { animate, useInView } from "framer-motion";
 
function useCounter({ from = 0, to, duration }) {
  const ref = React.useRef(null);
  const isInView = useInView(ref, {
    once: true,
  });
  const [value, setValue] = React.useState(from);
 
  React.useEffect(() => {
    if (!isInView) {
      return;
    }
    const controls = animate(from, to, {
      duration,
      onUpdate(value) {
        setValue(value);
      },
    });
 
    return () => controls.stop();
  }, [isInView, from, to, duration]);
 
  return {
    ref,
    count: Intl.NumberFormat().format(value.toFixed(0)),
  };
}
import * as React from "react";
import { animate, useInView } from "framer-motion";
 
function useCounter({ from = 0, to, duration }) {
  const ref = React.useRef(null);
  const isInView = useInView(ref, {
    once: true,
  });
  const [value, setValue] = React.useState(from);
 
  React.useEffect(() => {
    if (!isInView) {
      return;
    }
    const controls = animate(from, to, {
      duration,
      onUpdate(value) {
        setValue(value);
      },
    });
 
    return () => controls.stop();
  }, [isInView, from, to, duration]);
 
  return {
    ref,
    count: Intl.NumberFormat().format(value.toFixed(0)),
  };
}

Usage

import useCounter from "hooks/useCounter";
 
export default function App() {
  const { ref, count } = useCounter({
    from: 0,
    to: 1000,
    duration: 2,
  });
  return (
    <div className="App">
      <p ref={ref}>{count}</p>
    </div>
  );
}
import useCounter from "hooks/useCounter";
 
export default function App() {
  const { ref, count } = useCounter({
    from: 0,
    to: 1000,
    duration: 2,
  });
  return (
    <div className="App">
      <p ref={ref}>{count}</p>
    </div>
  );
}

View demo.