Files
ragusaitweb/src/hooks/useTypingEffect.ts
google-labs-jules[bot] 8f820e262f 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.
2026-01-22 04:43:55 +00:00

73 lines
1.7 KiB
TypeScript

import { useState, useEffect } from 'react';
interface UseTypingEffectOptions {
words: string[];
typingSpeed?: number;
deletingSpeed?: number;
pauseDuration?: number;
}
export function useTypingEffect({
words,
typingSpeed = 100,
deletingSpeed = 50,
pauseDuration = 2000,
}: UseTypingEffectOptions) {
const [currentWordIndex, setCurrentWordIndex] = useState(0);
const [currentText, setCurrentText] = useState('');
const [isDeleting, setIsDeleting] = useState(false);
const [isPaused, setIsPaused] = useState(false);
useEffect(() => {
let timer: ReturnType<typeof setTimeout>;
if (isPaused) {
timer = setTimeout(() => {
setIsPaused(false);
setIsDeleting(true);
}, pauseDuration);
} else {
const currentWord = words[currentWordIndex];
const isWordDeleted = isDeleting && currentText === '';
if (isWordDeleted) {
setIsDeleting(false);
setCurrentWordIndex((prev) => (prev + 1) % words.length);
} 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);
}
}
return () => clearTimeout(timer);
}, [
currentText,
isDeleting,
isPaused,
currentWordIndex,
words,
typingSpeed,
deletingSpeed,
pauseDuration,
]);
return {
text: currentText,
isTyping: !isDeleting && !isPaused,
isDeleting,
currentWordIndex,
};
}