skip to Main Content

I am trying to print out a raising text segment in React, but every char gets printed twice.

So hopefully someone has an idea.

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';

import Test from './test.js';

function Root(){
    return(
        <React.StrictMode>
            <Test/>
        </React.StrictMode>
    )
}

ReactDOM.createRoot(document.getElementById('root')).render(<Root/>);

test.js

import {useCallback,useEffect,useState} from 'react';

export default function Test(){

    const [outText,setOutText] = useState('');

    const drawText = useCallback(text=>{
        let index=0;
        let textInterval;
        const callback = ()=>{
            if (index>=text.length-1){
                clearInterval(textInterval);
                return;
            }
            setOutText(t=>{
                if (text[index]==='n'){
                    return t+'<br>';
                } else {
                    return t+text[index];
                }
            });
            index++;
        };
        textInterval=setInterval(callback,20);
    });

    useEffect(()=>{
        const text = `
            a text is written here
            and should be printed step by step
        `.split('');
        drawText(text);
    },[])

    return (
        <div id="test">
            <div className="out-text" dangerouslySetInnerHTML={{__html:outText}} />
        </div>
    );

}

Every character should be appended to outText only once and not twice. Please avoid solutions that rely on index % 2 as they will not resolve my issue.

2

Answers


  1. I haven’t been able to debug your code and find the problem, but I’d like to offer an alternative solution. The following uses a hook to manage the value of the string.

    function useTextWriter(text: string, delay: number) {
      const [outText, setOutText] = useState("");
      const chars = useRef(text.split(""));
    
      useEffect(() => {
        const i = setInterval(() => {
          if (chars.current.length) {
            const char = chars.current.shift();
            setOutText(`${outText}${char}`);
          }
        }, delay);
    
        return () => clearInterval(i);
      });
    
      return outText;
    }
    

    Example usage would be:

    export default function Test() {
      const value = useTextWriter(`a text`, 1000);
    
      return (
        <div id="test">
          <div className="out-text">
            {value}
          </div>
        </div>
      );
    }
    
    Login or Signup to reply.
  2. A more React idiomatic solution:

    const text = ["a text is written here", "and should be printed step by step"];
    
    function App() {
      const [index, setIndex] = React.useState(-1);
    
      React.useEffect(() => {
        if (index >= text.join("").length) {
          return;
        }
        setTimeout(() => setIndex((prev) => prev + 1), 100);
      }, [index]);
    
      return (
        <div className="App">
          <div className="text">
            {text[0].split("").map((x, i) => (
              <span className={i < index ? "visible" : ""} key={`${x}-${i}`}>
                {x}
              </span>
            ))}
          </div>
          <div className="text">
            {text[1].split("").map((x, i) => (
              <span
                className={i + text[0].length < index ? "visible" : ""}
                key={`${x}-${i}`}
              >
                {x}
              </span>
            ))}
          </div>
        </div>
      );
    }
    
    ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
    * {
      box-sizing: border-box;
    }
    
    html {
      font-size: 16px;
      font-family: Arial, Helvetica, sans-serif;
    }
    
    body {
      margin: 0;
    }
    
    .text span {
      opacity: 0;
      transition: opacity 0.1s ease-in-out;
    }
    
    .text span.visible {
      opacity: 1;
    }
       <div id="root"></div>
       <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search