react-countup
Version:
A React component wrapper around CountUp.js
198 lines (167 loc) • 4.77 kB
JavaScript
import Count from 'countup.js';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import warning from 'warning';
class CountUp extends Component {
static propTypes = {
decimal: PropTypes.string,
decimals: PropTypes.number,
delay: PropTypes.number,
easingFn: PropTypes.func,
end: PropTypes.number.isRequired,
formattingFn: PropTypes.func,
onEnd: PropTypes.func,
onStart: PropTypes.func,
prefix: PropTypes.string,
redraw: PropTypes.bool,
separator: PropTypes.string,
start: PropTypes.number,
suffix: PropTypes.string,
style: PropTypes.object,
useEasing: PropTypes.bool,
};
static defaultProps = {
decimal: '.',
decimals: 0,
delay: null,
duration: null,
easingFn: null,
formattingFn: null,
onEnd: () => {},
onPauseResume: () => {},
onReset: () => {},
onStart: () => {},
onUpdate: () => {},
prefix: '',
redraw: false,
separator: '',
start: 0,
suffix: '',
style: undefined,
useEasing: true,
};
componentDidMount() {
const { children, delay } = this.props;
this.instance = this.createInstance();
// Don't invoke start if component is used as a render prop
if (typeof children === 'function' && delay !== 0) return;
// Otherwise just start immediately
this.start();
}
shouldComponentUpdate(nextProps) {
const hasCertainPropsChanged =
this.props.duration !== nextProps.duration ||
this.props.end !== nextProps.end ||
this.props.start !== nextProps.start;
return hasCertainPropsChanged || this.props.redraw;
}
componentDidUpdate(prevProps) {
// If duration or start has changed, there's no way to update the duration
// or start value. So we need to re-create the CountUp instance in order to
// restart it.
if (
this.props.duration !== prevProps.duration ||
this.props.start !== prevProps.start
) {
this.instance = this.createInstance();
this.start();
}
// Only end value has changed, so reset and and re-animate with the updated
// end value.
if (this.props.end !== prevProps.end) {
this.instance.reset();
this.instance.update(this.props.end);
}
}
createInstance = () => {
if (typeof this.props.children === 'function') {
// Warn when user didn't use containerRef at all
warning(
this.containerRef.current &&
this.containerRef.current instanceof HTMLElement,
`Couldn't find attached element to hook the CountUp instance into! Try to attach "containerRef" from the render prop to a an HTMLElement, eg. <span ref={containerRef} />.`,
);
}
const {
decimal,
decimals,
duration,
easingFn,
end,
formattingFn,
prefix,
separator,
start,
suffix,
useEasing,
} = this.props;
return new Count(
this.containerRef.current,
start,
end,
decimals,
duration,
{
decimal,
easingFn,
formattingFn,
separator,
prefix,
suffix,
useEasing,
useGrouping: !!separator,
},
);
};
pauseResume = () => {
const { reset, restart: start, update } = this;
const { onPauseResume } = this.props;
this.instance.pauseResume();
onPauseResume({ reset, start, update });
};
reset = () => {
const { pauseResume, restart: start, update } = this;
const { onReset } = this.props;
this.instance.reset();
onReset({ pauseResume, start, update });
};
restart = () => {
this.reset();
this.start();
};
start = () => {
const { pauseResume, reset, restart: start, update } = this;
const { delay, onEnd, onStart } = this.props;
const run = () =>
this.instance.start(() => onEnd({ pauseResume, reset, start, update }));
// Delay start if delay prop is properly set
if (delay > 0) {
setTimeout(run, delay * 1000);
} else {
run();
}
onStart({ pauseResume, reset, update });
};
update = newEnd => {
const { pauseResume, reset, restart: start } = this;
const { onUpdate } = this.props;
this.instance.update(newEnd);
onUpdate({ pauseResume, reset, start });
};
containerRef = React.createRef();
render() {
const { children, className, style } = this.props;
const { containerRef, pauseResume, reset, restart, update } = this;
if (typeof children === 'function') {
return children({
countUpRef: containerRef,
pauseResume,
reset,
start: restart,
update,
});
}
return <span className={className} ref={containerRef} style={style} />;
}
}
export default CountUp;