why is it not adding second index element to the currrentText

Viewed 77

i am trying to perform typewriting effect through hooks. consider the text given as hello world . it is rendering only hlo world . it is not adding element at index 1 to the currentText. here is my code

import React,{useEffect,useState,useRef} from 'react';

function Typewriter({text}) {
    const index=useRef(0)
    const [currentText,setCurrentText]=useState('');
    useEffect(() => {
        const timeOutId=setTimeout( () =>{
            setCurrentText((value) => {
                return value+text.charAt(index.current)
            });
            index.current +=1;
        },1000);
        return () =>{
            clearTimeout(timeOutId);
        }
        
    },[currentText,text])

  return (
    <p>{currentText}</p>
  )
}
1 Answers

This is weird, but the solution is simple. The useEffect already depends on the currentText, so use it instead of the functional update option of set state:

const { useEffect,useState,useRef } = React;

function Typewriter({text}) {
  const index = useRef(0)
  const [currentText, setCurrentText] = useState('');

  useEffect(() => {
    const timeOutId = setTimeout(() => {
      setCurrentText(currentText + text.charAt(index.current));
      index.current +=1;
    }, 1000);

    return () => {
      clearTimeout(timeOutId);
    } 
  }, [currentText, text])

  return (
    <p>{currentText}</p>
  )
}

ReactDOM.createRoot(root)
  .render(
    <Typewriter text="hello world" />
  )
<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>

Not related, but a bit cleaner version of the type writer is to increment the index, and slice the required sub-string:

const { useEffect,useState,useRef } = React;

function Typewriter({text}) {
  const [index, setIndex] = useState(0);

  useEffect(() => {
    const timeOutId = setTimeout(() => {
      setIndex(index + 1);
    }, 1000);

    return () => {
      clearTimeout(timeOutId);
    } 
  }, [index])

  return (
    <p>{text.substring(0, index)}</p>
  )
}

ReactDOM.createRoot(root)
  .render(
    <Typewriter text="hello world" />
  )
<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>

Related