Refactor useTypingEffect to fix timer leak and optimize re-renders #5
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
interface UseTypingEffectOptions {
|
interface UseTypingEffectOptions {
|
||||||
words: string[];
|
words: string[];
|
||||||
@@ -18,25 +18,33 @@ export function useTypingEffect({
|
|||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [isPaused, setIsPaused] = useState(false);
|
const [isPaused, setIsPaused] = useState(false);
|
||||||
|
|
||||||
|
// Stabilize words array to prevent unnecessary effect resets on re-renders
|
||||||
|
// when the array reference changes but content remains the same.
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const stableWords = useMemo(() => words, [JSON.stringify(words)]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timer: ReturnType<typeof setTimeout>;
|
let timer: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
if (isPaused) {
|
const currentWord = stableWords[currentWordIndex];
|
||||||
timer = setTimeout(() => {
|
|
||||||
setIsPaused(false);
|
|
||||||
setIsDeleting(true);
|
|
||||||
}, pauseDuration);
|
|
||||||
} else {
|
|
||||||
const currentWord = words[currentWordIndex];
|
|
||||||
const isWordDeleted = isDeleting && currentText === '';
|
const isWordDeleted = isDeleting && currentText === '';
|
||||||
|
|
||||||
if (isWordDeleted) {
|
// If word is fully deleted, move to next word immediately (no timer)
|
||||||
|
if (!isPaused && isWordDeleted) {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
setCurrentWordIndex((prev) => (prev + 1) % words.length);
|
setCurrentWordIndex((prev) => (prev + 1) % stableWords.length);
|
||||||
} else {
|
return;
|
||||||
const speed = isDeleting ? deletingSpeed : typingSpeed;
|
}
|
||||||
|
|
||||||
|
// Determine speed based on state
|
||||||
|
const speed = isPaused ? pauseDuration : (isDeleting ? deletingSpeed : typingSpeed);
|
||||||
|
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
|
if (isPaused) {
|
||||||
|
setIsPaused(false);
|
||||||
|
setIsDeleting(true);
|
||||||
|
} else {
|
||||||
|
// Typing logic
|
||||||
if (isDeleting) {
|
if (isDeleting) {
|
||||||
setCurrentText((prev) => prev.substring(0, prev.length - 1));
|
setCurrentText((prev) => prev.substring(0, prev.length - 1));
|
||||||
} else {
|
} else {
|
||||||
@@ -47,9 +55,8 @@ export function useTypingEffect({
|
|||||||
setIsPaused(true);
|
setIsPaused(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, speed);
|
}, speed);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [
|
}, [
|
||||||
@@ -57,7 +64,7 @@ export function useTypingEffect({
|
|||||||
isDeleting,
|
isDeleting,
|
||||||
isPaused,
|
isPaused,
|
||||||
currentWordIndex,
|
currentWordIndex,
|
||||||
words,
|
stableWords,
|
||||||
typingSpeed,
|
typingSpeed,
|
||||||
deletingSpeed,
|
deletingSpeed,
|
||||||
pauseDuration,
|
pauseDuration,
|
||||||
|
|||||||
Reference in New Issue
Block a user