Fix uncleaned setTimeout in useTypingEffect hook
Refactor useTypingEffect to use a single useEffect with proper cleanup for all timers, preventing state updates on unmounted components. Add unit tests to verify behavior and ensure no memory leaks on unmount.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface UseTypingEffectOptions {
|
||||
words: string[];
|
||||
@@ -18,38 +18,50 @@ export function useTypingEffect({
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
|
||||
const tick = useCallback(() => {
|
||||
const currentWord = words[currentWordIndex];
|
||||
useEffect(() => {
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
|
||||
if (isPaused) {
|
||||
return;
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
setIsPaused(false);
|
||||
setIsDeleting(true);
|
||||
}, pauseDuration);
|
||||
} else {
|
||||
const currentWord = words[currentWordIndex];
|
||||
const isWordDeleted = isDeleting && currentText === '';
|
||||
|
||||
if (isDeleting) {
|
||||
setCurrentText(currentWord.substring(0, currentText.length - 1));
|
||||
|
||||
if (currentText.length === 0) {
|
||||
if (isWordDeleted) {
|
||||
setIsDeleting(false);
|
||||
setCurrentWordIndex((prev) => (prev + 1) % words.length);
|
||||
}
|
||||
} else {
|
||||
setCurrentText(currentWord.substring(0, currentText.length + 1));
|
||||
|
||||
if (currentText === currentWord) {
|
||||
setIsPaused(true);
|
||||
setTimeout(() => {
|
||||
setIsPaused(false);
|
||||
setIsDeleting(true);
|
||||
}, pauseDuration);
|
||||
} else {
|
||||
const speed = isDeleting ? deletingSpeed : typingSpeed;
|
||||
|
||||
timer = setTimeout(() => {
|
||||
if (isDeleting) {
|
||||
setCurrentText((prev) => prev.substring(0, prev.length - 1));
|
||||
} else {
|
||||
const nextText = currentWord.substring(0, currentText.length + 1);
|
||||
setCurrentText(nextText);
|
||||
|
||||
if (nextText === currentWord) {
|
||||
setIsPaused(true);
|
||||
}
|
||||
}
|
||||
}, speed);
|
||||
}
|
||||
}
|
||||
}, [currentText, currentWordIndex, isDeleting, isPaused, words, pauseDuration]);
|
||||
|
||||
useEffect(() => {
|
||||
const speed = isDeleting ? deletingSpeed : typingSpeed;
|
||||
const timer = setTimeout(tick, speed);
|
||||
return () => clearTimeout(timer);
|
||||
}, [tick, isDeleting, typingSpeed, deletingSpeed]);
|
||||
}, [
|
||||
currentText,
|
||||
isDeleting,
|
||||
isPaused,
|
||||
currentWordIndex,
|
||||
words,
|
||||
typingSpeed,
|
||||
deletingSpeed,
|
||||
pauseDuration,
|
||||
]);
|
||||
|
||||
return {
|
||||
text: currentText,
|
||||
|
||||
Reference in New Issue
Block a user