I have a piece of code which needs to be rendered using a typewriter effect. Basically, they are API responses similar to a GPT app. The code I have does the trick but while rendering the first case, certain letters are missing. While rendering the second case, the first case seems correct with fixed letters and the second case is missing some letters. Please advice.
import React, { useState, useEffect } from 'react';
const useTypewriter = (text, speed = 50) => {
const [displayText, setDisplayText] = useState('');
const [isComplete, setIsComplete] = useState(false);
useEffect(() => {
if (!text) {
setDisplayText('');
setIsComplete(false);
return;
}
let i = 0;
setDisplayText(''); // Reset to empty to start fresh
setIsComplete(false);
const typingInterval = setInterval(() => {
if (i < text.length) {
setDisplayText(prevText => prevText + text.charAt(i));
i++;
} else {
setIsComplete(true);
clearInterval(typingInterval);
}
}, speed);
return () => {
clearInterval(typingInterval);
};
}, [text, speed]);
return { displayText, isComplete };
};
export function App(props) {
const [responses, setResponses] = useState([]);
const [currentTypingIndex, setCurrentTypingIndex] = useState(0);
useEffect(() => {
const sampleResponses = [
{
key: 'google',
text: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.`
},
{
key: 'troogle',
text: `It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters.`
},
{
key: 'another',
text: `Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old.`
}
];
setTimeout(() => {
setResponses(prev => [...prev, sampleResponses[0]]); // Add google at 10s
}, 10000);
setTimeout(() => {
setResponses(prev => [...prev, sampleResponses[1]]); // Add troogle at 15s
}, 15000);
setTimeout(() => {
setResponses(prev => [...prev, sampleResponses[2]]); // Add another at 5s
}, 5000);
}, []);
const currentResponse = responses[currentTypingIndex];
const { displayText, isComplete } = useTypewriter(
currentResponse ? currentResponse.text : '',
50
);
useEffect(() => {
if (isComplete && currentTypingIndex < responses.length - 1) {
setCurrentTypingIndex(prev => prev + 1);
}
}, [isComplete, currentTypingIndex, responses.length]);
return (
<div className="App">
<h1>Hello React.</h1>
<h2>Start editing to see some magic happen!</h2>
{responses.slice(0, currentTypingIndex + 1).map((response, index) => (
<div key={response.key}>
<h1>{response.key.charAt(0).toUpperCase() + response.key.slice(1)}</h1>
<p>
{index < currentTypingIndex
? response.text // Show full text for completed responses
: index === currentTypingIndex
? displayText // Show typing text for current response
: '' // Nothing for future responses
}
</p>
</div>
))}
</div>
);
}
export default App;
This is the playground I have: https://playcode.io/2334062
Well I have tried and debug your code, overall you did it correct but just minor issue i can see here in useTypewriter
if (i < text.length) {
setDisplayText(prevText => prevText + text.charAt(i));
i++;
} else {
setIsComplete(true);
clearInterval(typingInterval);
}
only thing I see is state updates are asynchronous, and i++ is performed before update callback is executed, now to fix this we need to increase i exactly after the state update, so lets put that i++ in setter callback like this
if (i < text.length) {
setDisplayText(prevText => prevText + text.charAt(i++));
} else {
setIsComplete(true);
clearInterval(typingInterval);
}
And it will work :), I hope it will be helpful , enjoy !