A configurable React component wrapper around CountUp.js for animated number counting effects
npx @tessl/cli install tessl/npm-react-countup@6.5.0React CountUp is a configurable React component wrapper around CountUp.js that provides animated number counting effects. It offers both declarative component usage and imperative hook-based control with full TypeScript support.
npm install react-countupimport CountUp, { useCountUp, type CountUpProps } from "react-countup";For CommonJS:
const CountUp = require("react-countup").default;
const { useCountUp } = require("react-countup");import React from "react";
import CountUp from "react-countup";
// Simple declarative usage
function App() {
return (
<div>
<CountUp end={100} duration={2} />
</div>
);
}
// With custom formatting
function FormattedCounter() {
return (
<CountUp
start={0}
end={1234567}
duration={3}
separator=","
prefix="$"
suffix=" USD"
decimals={2}
/>
);
}React CountUp is built around three key components:
React component wrapper providing declarative animated counting with extensive customization options and render prop support.
/**
* React component for animated number counting
*/
declare const CountUp: React.FC<CountUpProps>;
interface CountUpProps extends CommonProps, CallbackProps {
/** CSS class name for the container element */
className?: string;
/** Force redraw on every prop change */
redraw?: boolean;
/** Render prop function for custom rendering */
children?: (props: RenderCounterProps) => React.ReactNode;
/** Inline styles for the container element */
style?: React.CSSProperties;
/** Preserve current value when updating end value */
preserveValue?: boolean;
/** Additional props for the container span element */
containerProps?: React.ComponentPropsWithoutRef<'span'>;
}
interface RenderCounterProps extends CountUpApi {
/** Ref to attach to custom render element */
countUpRef: React.RefObject<HTMLElement>;
}Usage Examples:
import React from "react";
import CountUp from "react-countup";
// Standard usage
<CountUp end={100} duration={2} />
// With render prop for custom styling
<CountUp end={100} duration={2}>
{({ countUpRef, start, reset }) => (
<div>
<span ref={countUpRef} className="big-number" />
<button onClick={start}>Start</button>
<button onClick={reset}>Reset</button>
</div>
)}
</CountUp>
// With event callbacks
<CountUp
end={100}
duration={2}
onEnd={({ reset, start }) => console.log("Animation completed!")}
onStart={() => console.log("Animation started!")}
/>Hook providing imperative control over CountUp animations with lifecycle management and programmatic API access.
/**
* Hook for imperative CountUp control
* @param props Configuration options for the counter
* @returns API object with control methods
*/
function useCountUp(props: UseCountUpProps): CountUpApi;
interface UseCountUpProps extends CommonProps, CallbackProps {
/** Target element reference (string selector or React ref) */
ref: string | React.RefObject<HTMLElement>;
/** Whether to reinitialize when props change */
enableReinitialize?: boolean;
}
interface CountUpApi {
/** Start or restart the animation */
start: () => void;
/** Pause or resume the animation */
pauseResume: () => void;
/** Reset the counter to start value */
reset: () => void;
/** Update the end value and animate to it */
update: (newEnd: string | number) => void;
/** Get the underlying CountUp.js instance */
getCountUp: (recreate?: boolean) => CountUpInstance;
}Usage Examples:
import React, { useRef } from "react";
import { useCountUp } from "react-countup";
function CustomCounter() {
const countUpRef = useRef<HTMLSpanElement>(null);
const { start, reset, update, pauseResume } = useCountUp({
ref: countUpRef,
end: 100,
duration: 2,
startOnMount: false,
});
return (
<div>
<span ref={countUpRef} />
<button onClick={start}>Start</button>
<button onClick={pauseResume}>Pause/Resume</button>
<button onClick={reset}>Reset</button>
<button onClick={() => update(200)}>Update to 200</button>
</div>
);
}
// With string selector
function SelectorCounter() {
useCountUp({
ref: 'counter-element',
end: 100,
duration: 2,
});
return <div id="counter-element" />;
}Shared configuration options available to both component and hook.
interface CommonProps extends CountUpInstanceProps {
/** Whether to start animation on mount */
startOnMount?: boolean;
/** Delay before starting animation (in seconds) */
delay?: number | null;
}
interface CountUpInstanceProps extends CountUpOptions {
/** Number of decimal places to display */
decimals?: number;
/** The target end value */
end: number;
/** The starting value */
start?: number;
/** Whether to use easing animation */
useEasing?: boolean;
}Complete event callback system for animation lifecycle management.
interface CallbackProps {
/** Called when animation ends */
onEnd?: OnEndCallback;
/** Called when animation starts */
onStart?: OnStartCallback;
/** Called when animation is paused/resumed */
onPauseResume?: OnPauseResumeCallback;
/** Called when counter is reset */
onReset?: OnResetCallback;
/** Called when end value is updated */
onUpdate?: OnUpdateCallback;
}
type OnEndCallback = (args: OnEndArgs) => void;
type OnStartCallback = (args: OnStartArgs) => void;
type OnPauseResumeCallback = (args: OnPauseResumeArgs) => void;
type OnResetCallback = (args: OnResetArgs) => void;
type OnUpdateCallback = (args: OnUpdateArgs) => void;
interface OnEndArgs {
pauseResume: () => void;
reset: () => void;
start: () => void;
update: (newEnd: string | number) => void;
}
interface OnStartArgs {
pauseResume: () => void;
reset: () => void;
update: (newEnd: string | number) => void;
}
interface OnPauseResumeArgs {
reset: () => void;
start: () => void;
update: (newEnd: string | number) => void;
}
interface OnResetArgs {
pauseResume: () => void;
start: () => void;
update: (newEnd: string | number) => void;
}
interface OnUpdateArgs {
pauseResume: () => void;
reset: () => void;
start: () => void;
}All configuration options from the underlying CountUp.js library are supported:
interface CountUpOptions {
/** Decimal character */
decimal?: string;
/** Thousands separator character */
separator?: string;
/** Text prepended to result */
prefix?: string;
/** Text appended to result */
suffix?: string;
/** Animation duration in seconds */
duration?: number;
/** Whether to use easing animation */
useEasing?: boolean;
/** Whether to use thousands separator grouping */
useGrouping?: boolean;
/** Whether to use Indian number separators */
useIndianSeparators?: boolean;
/** Custom easing function */
easingFn?: (t: number, b: number, c: number, d: number) => number;
/** Custom formatting function */
formattingFn?: (value: number) => string;
/** Custom numerals array */
numerals?: string[];
/** Whether to enable scroll spy functionality */
enableScrollSpy?: boolean;
/** Scroll spy delay in milliseconds */
scrollSpyDelay?: number;
/** Whether scroll spy should trigger only once */
scrollSpyOnce?: boolean;
/** CountUp.js plugin */
plugin?: any;
}
/** CountUp.js instance returned by getCountUp method */
interface CountUpInstance {
/** Start the animation */
start(callback?: () => void): void;
/** Pause or resume the animation */
pauseResume(): void;
/** Reset to start value */
reset(): void;
/** Update to new end value */
update(newEnd: number | string): void;
/** Get formatted value at current state */
formattingFn(value: number): string;
}The useCountUp hook provides sensible defaults for all configuration options:
const DEFAULTS = {
decimal: '.',
separator: ',',
delay: null,
prefix: '',
suffix: '',
duration: 2,
start: 0,
decimals: 0,
startOnMount: true,
enableReinitialize: true,
useEasing: true,
useGrouping: true,
useIndianSeparators: false,
};import React, { useState } from "react";
import CountUp from "react-countup";
function ConditionalCounter() {
const [shouldAnimate, setShouldAnimate] = useState(false);
return (
<div>
{shouldAnimate && (
<CountUp
end={100}
duration={2}
onEnd={() => setShouldAnimate(false)}
/>
)}
<button onClick={() => setShouldAnimate(true)}>
Start Animation
</button>
</div>
);
}import React, { useRef } from "react";
import { useCountUp } from "react-countup";
function SynchronizedCounters() {
const ref1 = useRef<HTMLSpanElement>(null);
const ref2 = useRef<HTMLSpanElement>(null);
const counter1 = useCountUp({
ref: ref1,
end: 100,
duration: 2,
startOnMount: false,
});
const counter2 = useCountUp({
ref: ref2,
end: 200,
duration: 2,
startOnMount: false,
});
const startBoth = () => {
counter1.start();
counter2.start();
};
return (
<div>
<span ref={ref1} /> | <span ref={ref2} />
<button onClick={startBoth}>Start Both</button>
</div>
);
}import CountUp from "react-countup";
function CustomFormattedCounter() {
return (
<CountUp
end={1234567.89}
decimals={2}
duration={3}
formattingFn={(value) => {
// Custom currency formatting
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(value);
}}
/>
);
}The library handles common error scenarios:
countUpRef is not attached to a DOM elementuseIsomorphicLayoutEffect to prevent hydration mismatchesReact CountUp provides complete TypeScript definitions with: