diff --git a/src/components/TextRotater/TextRotater.jsx b/src/components/TextRotater/TextRotater.jsx index 8487804b06a7..9649b28b560f 100644 --- a/src/components/TextRotater/TextRotater.jsx +++ b/src/components/TextRotater/TextRotater.jsx @@ -1,104 +1,89 @@ import { clsx } from "clsx"; import PropTypes from "prop-types"; -import { Children, PureComponent, cloneElement } from "react"; +import { + Children, + cloneElement, + memo, + useCallback, + useEffect, + useRef, + useState, +} from "react"; -export default class TextRotater extends PureComponent { - static defaultProps = { - delay: 0, - repeatDelay: 3000, - }; +function TextRotater({ children, delay = 0, repeatDelay = 3000, maxWidth }) { + const [currentIndex, setCurrentIndex] = useState(0); + const [contentHeight, setContentHeight] = useState(0); + const [isAnimating, setIsAnimating] = useState(false); + const contentNodeRef = useRef(null); + const heightTimeoutRef = useRef(null); + const animationTimeoutRef = useRef(null); + const repeatTimeoutRef = useRef(null); - static propTypes = { - children: PropTypes.arrayOf(PropTypes.node), - delay: PropTypes.number, - repeatDelay: PropTypes.number, - // Needed to prevent jump when - // rotating between texts of different widths - maxWidth: PropTypes.number, - }; + const contentCallbackRef = useCallback((node) => { + contentNodeRef.current = node; + }, []); - state = { - currentIndex: 0, - contentHeight: 0, - isAnimating: false, - }; + useEffect(() => { + const calculateHeight = () => { + if (contentNodeRef.current) { + setContentHeight(contentNodeRef.current.clientHeight); + } + }; - render() { - const { children, maxWidth } = this.props; - const { currentIndex, contentHeight, isAnimating } = this.state; + heightTimeoutRef.current = setTimeout(calculateHeight, 50); + animationTimeoutRef.current = setTimeout(() => setIsAnimating(true), delay); + + window.addEventListener("resize", calculateHeight); + + return () => { + clearTimeout(heightTimeoutRef.current); + clearTimeout(animationTimeoutRef.current); + clearTimeout(repeatTimeoutRef.current); + window.removeEventListener("resize", calculateHeight); + }; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const handleTransitionEnd = useCallback(() => { const childrenCount = Children.count(children); + setCurrentIndex((prev) => (prev + 1) % childrenCount); + setIsAnimating(false); - const currentChild = cloneElement(children[currentIndex], { - ref: (child) => (this.content = child), - }); + repeatTimeoutRef.current = setTimeout(() => { + setIsAnimating(true); + }, repeatDelay); + }, [children, repeatDelay]); - const nextChild = cloneElement( - children[(currentIndex + 1) % childrenCount], - ); + const childrenCount = Children.count(children); + const nextChild = cloneElement(children[(currentIndex + 1) % childrenCount]); - return ( + return ( +
-
- {currentChild} - {nextChild} -
+ {children[currentIndex]} + {nextChild}
- ); - } - - componentDidMount() { - const { delay } = this.props; - - this.heightTimeout = setTimeout(() => { - this._calculateContentHeight(); - }, 50); - - this.animationTimeout = setTimeout(() => { - this.setState({ isAnimating: true }); - }, delay); - - window.addEventListener("resize", this._calculateContentHeight); - } - - componentWillUnmount() { - clearTimeout(this.heightTimeout); - clearTimeout(this.animationTimeout); - clearTimeout(this.repeatTimeout); - window.removeEventListener("resize", this._calculateContentHeight); - } - - _calculateContentHeight = () => { - if (this.content) { - this.setState({ - contentHeight: this.content.clientHeight, - }); - } - }; +
+ ); +} - _handleTransitionEnd = () => { - const { children, repeatDelay } = this.props; +TextRotater.propTypes = { + children: PropTypes.arrayOf(PropTypes.node), + delay: PropTypes.number, + repeatDelay: PropTypes.number, + // Needed to prevent jump when + // rotating between texts of different widths + maxWidth: PropTypes.number, +}; - this.setState( - { - currentIndex: (this.state.currentIndex + 1) % Children.count(children), - isAnimating: false, - }, - () => { - this.repeatTimeout = setTimeout(() => { - this.setState({ isAnimating: true }); - }, repeatDelay); - }, - ); - }; -} +export default memo(TextRotater);