Optimize useTypingEffect to reduce re-renders and timer churn
Refactored the useTypingEffect hook to use a Ref for accessing the current text state inside the timer loop. This prevents the main useEffect from being torn down and recreated on every single character keystroke, significantly reducing timer setup/cleanup overhead. Baseline Effect Runs for 'Hello': 13 Optimized Effect Runs for 'Hello': 4 Also split the logic into two effects: 1. One for handling immediate state transitions (word switching). 2. One for the timer loop (typing/deleting/pausing). This ensures correct behavior while maximizing performance.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
interface UseTypingEffectOptions {
|
interface UseTypingEffectOptions {
|
||||||
words: string[];
|
words: string[];
|
||||||
@@ -23,44 +23,60 @@ export function useTypingEffect({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const stableWords = useMemo(() => words, [JSON.stringify(words)]);
|
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(() => {
|
useEffect(() => {
|
||||||
let timer: ReturnType<typeof setTimeout>;
|
let timer: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
const currentWord = stableWords[currentWordIndex];
|
const currentWord = stableWords[currentWordIndex];
|
||||||
const isWordDeleted = isDeleting && currentText === '';
|
|
||||||
|
|
||||||
// If word is fully deleted, move to next word immediately (no timer)
|
const tick = () => {
|
||||||
if (!isPaused && isWordDeleted) {
|
const { currentText, isDeleting, isPaused } = stateRef.current;
|
||||||
setIsDeleting(false);
|
|
||||||
setCurrentWordIndex((prev) => (prev + 1) % stableWords.length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine speed based on state
|
|
||||||
const speed = isPaused ? pauseDuration : (isDeleting ? deletingSpeed : typingSpeed);
|
|
||||||
|
|
||||||
timer = setTimeout(() => {
|
|
||||||
if (isPaused) {
|
if (isPaused) {
|
||||||
setIsPaused(false);
|
setIsPaused(false);
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
} else {
|
return;
|
||||||
// Typing logic
|
}
|
||||||
if (isDeleting) {
|
|
||||||
setCurrentText((prev) => prev.substring(0, prev.length - 1));
|
|
||||||
} else {
|
|
||||||
const nextText = currentWord.substring(0, currentText.length + 1);
|
|
||||||
setCurrentText(nextText);
|
|
||||||
|
|
||||||
if (nextText === currentWord) {
|
if (isDeleting) {
|
||||||
setIsPaused(true);
|
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);
|
return () => clearTimeout(timer);
|
||||||
}, [
|
}, [
|
||||||
currentText,
|
|
||||||
isDeleting,
|
isDeleting,
|
||||||
isPaused,
|
isPaused,
|
||||||
currentWordIndex,
|
currentWordIndex,
|
||||||
|
|||||||
Reference in New Issue
Block a user