1

My problem is, that I want to show word by word on the website, iterating through a text. I am using state variables to append the words, set by useEffect.

What I did is, that I call useEffect() , write first word in a state variable and by doing this trigger useEFfect again to write the next word into state variable. In the render section I just show the state variable and it should show the whole text word by word. I initially added also an interval to set speed, but just commented it out to test. In the console.log I can see the index increasing correctly and words are read correctly from words array, but the second word is missing in the state variable (btw. I also tried that with the letters and it was also second index not shown in state variable and additionally sometimes even more letters messed up). Generally it seems to be some kind of timing issue, but I do not really understand, why it is not working, no matter what interval is set...hope someone can help.

This is my code:

  let index = useRef(0);
  const words = text.split(" ");

  useEffect(() => {
    const tick = () => {
      console.log("Index: ", index.current);
      console.log("Length: ", words.length);
      console.log("Char: ", words[index.current]);
      console.log("placeholder: ", placeholder);

      setPlaceholder((prev) => prev + " " + words[index.current]);
      index.current++;
    };
    if (index.current < words.length - 1) {
      /* let addChar = setInterval(tick, 70);
      return () => clearInterval(addChar); */
      tick();
    }
  }, [placeholder]);

---some other stuff----

return
<div>{placeholder}</div>
Stefan T.
  • 13
  • 2

1 Answers1

1

Because setting a state is an asynchronous process, the index gets updated out of sync. Start the index at -1 and increment it before you actually return the new state.

Note: your useEffect is dependent on placeholder, which changes each tick. This clears the interval on each tick, which amounts to using a timeout.

const { useState, useRef, useEffect } = React;

const Demo = ({ text }) => {
  const [placeholder, setPlaceholder] = useState("");
  const index = useRef(-1);
  const words = text.split(" ");

  useEffect(() => {
    const tick = () => {
      setPlaceholder((prev) => {
        index.current++;
        return prev + " " + words[index.current]
      });      
    };
    if (index.current < words.length - 1) {
      const addChar = setTimeout(tick, 70);
      return () => clearTimeout(addChar);
    }
  }, [placeholder]);

  return <div>{placeholder}</div>;
}


ReactDOM
  .createRoot(root)
  .render(<Demo text="Welcome to NY and have a nice day" />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

<div id="root"></div>

However, I would just increment an index state, and create the placeholder on each render:

const { useState, useMemo, useEffect } = React;

const Demo = ({ text }) => {
  const [index, setIndex] = useState(0);
  const words = useMemo(() => text.split(' '), [text]);
  
  const placeholder = words.slice(0, index).join(' ');

  useEffect(() => {
    if(index >= words.length) return;
    
    const timeout = setTimeout(() => setIndex(i => i + 1), 70);
    
    return () => {
      clearTimeout(timeout);
    };
  }, [setIndex, index, words]);

  return <div>{placeholder}</div>;
}


ReactDOM
  .createRoot(root)
  .render(<Demo text="Welcome to NY and have a nice day" />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

<div id="root"></div>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • Thank you ! solved my problem. I still do not really grasp this async stuff it seems. I am not really sure about how this all works it seems. Do you have a good source, where to read in detail about that ? – Stefan T. Jan 26 '23 at 06:55
  • You're welcome :) Try this [answer](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately) and this [article](https://medium.com/codex/all-about-react-usestate-hook-54b432d61be3) – Ori Drori Jan 26 '23 at 14:07