I am implementing a typewriter effect in my React app where I fetch a string from a URL and display it one character at a time. I expect the effect to start from the first character (index 0) and proceed through all the characters sequentially. However, the second character (index 1) is skipped, and the rest of the characters are displayed correctly.
Additional Information:
The text state is updated correctly after fetching the data.
I’ve added a 500ms delay between the display of each character.
Any suggestions on improving the logic or debugging this issue would be appreciated!
import "./styles.css";
import React, { useEffect, useState } from "react";
export default function App() {
const [text, setText] = useState(""); // To hold the fetched text
const [displayText, setDisplayText] = useState(""); // For the typewriter effect
const [loading, setLoading] = useState(true); // Loading state
// Fetching text from the URL
useEffect(() => {
const fetchText = async () => {
try {
const response = await fetch("https://example-url.com/text"); // Fetch from URL
const data = await response.text();
setText(data); // Save the text in the state
setLoading(false); // Loading done
} catch (error) {
console.error("Error fetching the text:", error);
setLoading(false);
}
};
fetchText();
}, []);
// Typewriter effect logic
useEffect(() => {
if (!loading && text) {
let index = 0;
const interval = setInterval(() => {
setDisplayText((prevText) => prevText + text[index]);
index++;
if (index === text.length) {
clearInterval(interval); // Stop when all characters are shown
}
}, 500); // 500ms delay between characters
return () => clearInterval(interval); // Cleanup
}
}, [text, loading]);
// Rendering the text with a typewriter effect
return (
<div className="App">
{loading ? (
<p>Loading...</p> // Loading text
) : (
<ul>
{displayText.split("").map((char, index) => (
<li key={index}>{char}</li> // Render each character in a list
))}
</ul>
)}
</div>
);
}
I used setInterval
in useEffect
to append characters one by one from the fetched string. I expected the typewriter effect to start from the first character (index 0), but it skips the second one and displays rest correctly.
2
Answers
Issue
The issue here is that the effect/interval callback is mutating the index before the state update is processed. React state updates are enqueued and then processed when the function scope and callstack are completed.
index
is mutated to 1 before the first state update is processed to use thetext[0]
value.Solution
I suggest storing the index in state and incrementing that instead of copying parts of the
text
state into thedisplayText
state. You can compute the derived "display text" value from thetext
andindex
states.Example Implementation:
Demo:
I have revised your code to use a timeout instead of an interval, and I track the index with a ref. I have revised your code so we can test by setting the string to be displayed manually
I’m not 100% certain what your issue was.