-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Typescript-ify. * Converted `CountUp` to a Functional Component. Now just need to switch it to use `useCountUp`. * Fix this possibly backwards-incompatible type. * Fix instance config to only support hook. * Fix some effect issues. * The hook version and the component version used two different methods -- the component initialized immediately and set "0" as the value, and the hook delayed initialization and returned "" as the value. This breaks backwards compatibility by setting both to "0", though it may need to be re-thought. * Remove unnecessary prop. * Test fixes and some TypeScript fixes. * Add TS to build process. * Fix build script. * Check delay for undefined-ness as well. * Remove proptypes. * Timeout should use the return type of setTimeout, as it could be NodeJS.Timeout in node, or number in the browser. * Check for existing instance before checking for instance.target. * Fixed createInstance Element warning. * Fixed createInstance element warning (2) * Clean up package.json. * Remove @babel/plugin-class-properties as we no longer use a class. * Remove prop-types from rollup config. * Remove plugin-proposal-class-properties as we no longer use a class. * Use destructuring in useCountUp. * Missed a line from my previous commit.
- Loading branch information
Showing
10 changed files
with
216 additions
and
217 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,179 +1,97 @@ | ||
import React, { Component, CSSProperties } from 'react'; | ||
import { createCountUpInstance, DEFAULTS } from './common'; | ||
import { CountUp as CountUpJs } from 'countup.js'; | ||
import React, { CSSProperties, useEffect } from 'react'; | ||
import { CallbackProps, CommonProps, RenderCounterProps } from './types'; | ||
import { useEventCallback } from './helpers/useEventCallback'; | ||
import useCountUp from './useCountUp'; | ||
|
||
export interface CountUpProps extends CommonProps, CallbackProps { | ||
className?: string; | ||
redraw?: boolean; | ||
preserveValue?: boolean; | ||
children?: (props: RenderCounterProps) => React.ReactNode; | ||
style?: CSSProperties; | ||
preserveValue?: boolean; | ||
} | ||
|
||
class CountUp extends Component<CountUpProps> { | ||
private instance: CountUpJs | undefined; | ||
private timeoutId: ReturnType<typeof setTimeout> | undefined; | ||
|
||
static defaultProps = { | ||
...DEFAULTS, | ||
redraw: false, | ||
style: undefined, | ||
preserveValue: false, | ||
}; | ||
|
||
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(); | ||
} | ||
|
||
checkProps = (updatedProps: CountUpProps) => { | ||
const { | ||
start, | ||
suffix, | ||
prefix, | ||
redraw, | ||
duration, | ||
separator, | ||
decimals, | ||
decimal, | ||
className, | ||
formattingFn | ||
} = this.props; | ||
|
||
const hasPropsChanged = | ||
duration !== updatedProps.duration || | ||
start !== updatedProps.start || | ||
suffix !== updatedProps.suffix || | ||
prefix !== updatedProps.prefix || | ||
separator !== updatedProps.separator || | ||
decimals !== updatedProps.decimals || | ||
decimal !== updatedProps.decimal || | ||
className !== updatedProps.className || | ||
formattingFn !== updatedProps.formattingFn; | ||
|
||
return hasPropsChanged || redraw; | ||
}; | ||
|
||
shouldComponentUpdate(nextProps: CountUpProps) { | ||
const { end } = this.props; | ||
return this.checkProps(nextProps) || end !== nextProps.end; | ||
} | ||
|
||
componentDidUpdate(prevProps: CountUpProps) { | ||
// If duration, suffix, prefix, separator or start has changed | ||
// there's no way to update the values. | ||
// So we need to re-create the CountUp instance in order to | ||
// restart it. | ||
const { end, preserveValue } = this.props; | ||
|
||
if (this.checkProps(prevProps)) { | ||
this.instance?.reset(); | ||
this.instance = this.createInstance(); | ||
this.start(); | ||
const CountUp = (props: CountUpProps) => { | ||
const { className, redraw, children, style, ...useCountUpProps } = props; | ||
const containerRef = React.useRef<any>(null); | ||
const isInitializedRef = React.useRef(false); | ||
|
||
const countUp = useCountUp({ | ||
...useCountUpProps, | ||
ref: containerRef, | ||
startOnMount: typeof children !== 'function' || props.delay === 0, | ||
// component manually restarts | ||
enableReinitialize: false, | ||
}); | ||
|
||
const restart = useEventCallback(() => { | ||
countUp.start(); | ||
}); | ||
|
||
const update = useEventCallback((end: string | number) => { | ||
if (!props.preserveValue) { | ||
countUp.reset(); | ||
} | ||
countUp.update(end); | ||
}); | ||
|
||
// Only end value has changed, so reset and and re-animate with the updated | ||
// end value. | ||
if (end !== prevProps.end) { | ||
if (!preserveValue) { | ||
this.instance?.reset(); | ||
} | ||
this.instance?.update(end); | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
if (this.timeoutId) { | ||
clearTimeout(this.timeoutId); | ||
} | ||
|
||
this.instance?.reset(); | ||
} | ||
|
||
createInstance = () => { | ||
if (typeof this.props.children === 'function') { | ||
useEffect(() => { | ||
if (typeof props.children === 'function') { | ||
// Warn when user didn't use containerRef at all | ||
if (!(this.containerRef.current instanceof Element)) { | ||
if (!(containerRef.current instanceof Element)) { | ||
console.error(`Couldn't find attached element to hook the CountUp instance into! Try to attach "containerRef" from the render prop to a an Element, eg. <span ref={containerRef} />.`); | ||
return; | ||
} | ||
} | ||
return createCountUpInstance(this.containerRef.current, this.props); | ||
}; | ||
|
||
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(); | ||
}; | ||
// unlike the hook, the CountUp component initializes on mount | ||
countUp.getCountUp(); | ||
}, []); | ||
|
||
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 && delay > 0) { | ||
this.timeoutId = setTimeout(run, delay * 1000); | ||
} else { | ||
run(); | ||
useEffect(() => { | ||
if (isInitializedRef.current) { | ||
update(props.end); | ||
} | ||
}, [props.end]); | ||
|
||
onStart?.({ pauseResume, reset, update }); | ||
}; | ||
|
||
update = (newEnd: string | number) => { | ||
const { pauseResume, reset, restart: start } = this; | ||
const { onUpdate } = this.props; | ||
|
||
this.instance?.update(newEnd); | ||
|
||
onUpdate?.({ pauseResume, reset, start }); | ||
}; | ||
|
||
containerRef = React.createRef<any>(); | ||
|
||
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, | ||
}); | ||
// if props.redraw, call this effect on every props change | ||
useEffect(() => { | ||
if (props.redraw && isInitializedRef.current) { | ||
restart(); | ||
} | ||
}, [props.redraw && props]); | ||
|
||
return <span className={className} ref={containerRef} style={style} />; | ||
// if not props.redraw, call this effect only when certain props are changed | ||
useEffect(() => { | ||
if (!props.redraw && isInitializedRef.current) { | ||
restart(); | ||
} | ||
}, [ | ||
props.redraw, | ||
props.start, | ||
props.suffix, | ||
props.prefix, | ||
props.duration, | ||
props.separator, | ||
props.decimals, | ||
props.decimal, | ||
props.className, | ||
props.formattingFn | ||
]); | ||
|
||
useEffect(() => { | ||
isInitializedRef.current = true; | ||
}, []); | ||
|
||
if (typeof children === 'function') { | ||
// TypeScript forces functional components to return JSX.Element | null. | ||
return children({ | ||
countUpRef: containerRef, | ||
...countUp, | ||
}) as JSX.Element | null; | ||
} | ||
|
||
return <span className={className} ref={containerRef} style={style} />; | ||
} | ||
|
||
export default CountUp; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { useCallback, useRef } from "react"; | ||
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"; | ||
|
||
/** | ||
* Create a stable reference to a callback which is updated after each render is committed. | ||
* Typed version borrowed from Formik v2.2.1. Licensed MIT. | ||
* | ||
* https://github.com/formium/formik/blob/9316a864478f8fcd4fa99a0735b1d37afdf507dc/LICENSE | ||
*/ | ||
export function useEventCallback<T extends (...args: any[]) => any>(fn: T): T { | ||
const ref: any = useRef(fn); | ||
|
||
// we copy a ref to the callback scoped to the current state/props on each render | ||
useIsomorphicLayoutEffect(() => { | ||
ref.current = fn; | ||
}); | ||
|
||
return useCallback( | ||
(...args: any[]) => ref.current.apply(void 0, args), | ||
[] | ||
) as T; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { useEffect, useLayoutEffect } from 'react'; | ||
|
||
/** | ||
* Silence SSR Warnings. | ||
* Borrowed from Formik v2.1.1, Licensed MIT. | ||
* | ||
* https://github.com/formium/formik/blob/9316a864478f8fcd4fa99a0735b1d37afdf507dc/LICENSE | ||
*/ | ||
export const useIsomorphicLayoutEffect = | ||
typeof window !== 'undefined' && | ||
typeof window.document !== 'undefined' && | ||
typeof window.document.createElement !== 'undefined' | ||
? useLayoutEffect | ||
: useEffect; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.