⚡ Optimize useTypingEffect timer performance #9
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
interface UseTypingEffectOptions {
|
||||
words: string[];
|
||||
@@ -23,44 +23,60 @@ export function useTypingEffect({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const stableWords = useMemo(() => words, [JSON.stringify(words)]);
|
||||
|
||||
// Ref to hold the latest state for the timer loop
|
||||
const stateRef = useRef({ currentText, isDeleting, isPaused });
|
||||
useEffect(() => {
|
||||
stateRef.current = { currentText, isDeleting, isPaused };
|
||||
});
|
||||
|
||||
// Effect 1: Handle word switching when deleted
|
||||
useEffect(() => {
|
||||
if (isDeleting && currentText === '' && !isPaused) {
|
||||
setIsDeleting(false);
|
||||
setCurrentWordIndex((prev) => (prev + 1) % stableWords.length);
|
||||
}
|
||||
}, [currentText, isDeleting, isPaused, stableWords]);
|
||||
|
||||
// Effect 2: Timer Loop
|
||||
useEffect(() => {
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
|
||||
const currentWord = stableWords[currentWordIndex];
|
||||
const isWordDeleted = isDeleting && currentText === '';
|
||||
|
||||
// If word is fully deleted, move to next word immediately (no timer)
|
||||
if (!isPaused && isWordDeleted) {
|
||||
setIsDeleting(false);
|
||||
setCurrentWordIndex((prev) => (prev + 1) % stableWords.length);
|
||||
return;
|
||||
}
|
||||
const tick = () => {
|
||||
const { currentText, isDeleting, isPaused } = stateRef.current;
|
||||
|
||||
// Determine speed based on state
|
||||
const speed = isPaused ? pauseDuration : (isDeleting ? deletingSpeed : typingSpeed);
|
||||
|
||||
timer = setTimeout(() => {
|
||||
if (isPaused) {
|
||||
setIsPaused(false);
|
||||
setIsDeleting(true);
|
||||
} else {
|
||||
// Typing logic
|
||||
if (isDeleting) {
|
||||
setCurrentText((prev) => prev.substring(0, prev.length - 1));
|
||||
} else {
|
||||
const nextText = currentWord.substring(0, currentText.length + 1);
|
||||
setCurrentText(nextText);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextText === currentWord) {
|
||||
setIsPaused(true);
|
||||
}
|
||||
if (isDeleting) {
|
||||
if (currentText === '') {
|
||||
// Handled by the other effect
|
||||
return;
|
||||
}
|
||||
setCurrentText((prev) => prev.substring(0, prev.length - 1));
|
||||
timer = setTimeout(tick, deletingSpeed);
|
||||
} else {
|
||||
const nextText = currentWord.substring(0, currentText.length + 1);
|
||||
setCurrentText(nextText);
|
||||
|
||||
if (nextText === currentWord) {
|
||||
setIsPaused(true);
|
||||
} else {
|
||||
timer = setTimeout(tick, typingSpeed);
|
||||
}
|
||||
}
|
||||
}, speed);
|
||||
};
|
||||
|
||||
// Determine initial speed
|
||||
const speed = isPaused ? pauseDuration : (isDeleting ? deletingSpeed : typingSpeed);
|
||||
timer = setTimeout(tick, speed);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [
|
||||
currentText,
|
||||
isDeleting,
|
||||
isPaused,
|
||||
currentWordIndex,
|
||||
|
||||
Reference in New Issue
Block a user