Merge pull request #9 from ragusa-it/perf/optimize-typing-effect-timer-13814216160386825632

 Optimize useTypingEffect timer performance
This commit was merged in pull request #9.
This commit is contained in:
Melvin Ragusa
2026-01-23 10:55:36 +01:00
committed by GitHub

View File

@@ -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
return;
}
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,