Home > Mobile >  React component only rendering twice and then stopping
React component only rendering twice and then stopping

Time:09-03

new to react, so bear with me.

Trying to make a animated typer with flashing | character at the end. The text will appear at random intervals but the | will be fixed.

Can anyone tell me why this isn't working. The flasher component works as expected, but the main text doesn't render, or renders one character then stops.

I've put a breakpoint on the useEffect in the Typer function and it only fires twice. But I've called updateTxt again?

I have tried to create a new object with updateTxt(Object.assign(txt) to see if that is the reason.

Maybe I'm doing it all wrong. Should I be using useRef and updating the element.current.innerText?

import {useEffect, useState} from "react";
import parse from 'html-react-parser';

function randomIntFromInterval(min, max) { // min and max included
    return Math.floor(Math.random() * (max - min   1)   min)
}

function Flasher(flash) {
    const [flashval, updateFlash] = useState("<span>&nbsp&nbsp</span>")
    useEffect(() => {
        if (flash) {
            setTimeout(() => {
                if (flashval === "&nbsp|") {
                    updateFlash("<span>&nbsp&nbsp</span>")
                } else {
                    updateFlash("&nbsp|")
                }
            }, 375)
        }
    })
    return (<>
            {parse(flashval)}
        </>)
}

export default function Typer({text, transform, flash, speed = 1}) {

    const [txt, updateTxt] = useState({options: text.split("|"), index: 0, current_text: ""})

    useEffect(() => {
        let min = 1, max = 4
        //get the full text required
        let current_full_text = txt.options[txt.index]
        //set the current text  1
        txt.current_text = current_full_text.substring(0, txt.current_text.length   1)
        // logic if need to switch text or not
        if (txt.current_text === current_full_text) {
            // this will wait at the end of each word
            txt.index  
            // increases the index of the array and if too many then it resets to 0 and reset current text
            if (txt.index > txt.options.length - 1) {
                txt.index = 0
                txt.current_text = ""
                min = 4
                max = 10
            }
        }
        setTimeout(() => {
            updateTxt(Object.assign(txt))
        }, randomIntFromInterval(min, max) * 100 * speed)
    })

    return (<span>{txt.current_text}<Flasher flash/></span>)
}
<Typer speed="0.5" flash={true} text="test text 1|test me now|why are you not working"/>

CodePudding user response:

There are several problems.

  1. The first parameter is missed for Object.assign:
updateTxt(Object.assign({}, txt));
  1. The dependency lists are missed for both effects. You also want to clean up the timers:
useEffect(() => {
  ...
  return () => clearTimeout(timer);
}, [flashval, flash]);
useEffect(() => {
  ...
  return () => clearTimeout(timer);
}, [speed, txt]);
  1. There is a bug. When index is changed this line txt.current_text.length 1 works incorrectly. You can reset current_text:
txt.index  ;
txt.current_text = "";

And move this part down:

txt.current_text = current_full_text.substring(
  0,
  txt.current_text.length   1
);

Working example

CodePudding user response:

An useEffect without a dependency (2nd argument) is run everytime.

In the Typer component, txt is a state property & hence should be updated only using updateTxt.

A setTimeout/setInterval should have the corresponding clearTimeout/clearInterval in the componentWillUnmount method or the returned callback of useEffect (both are called just before unmounting the component).

When updating a state property using its previous value (cf flashval), the callback argument of the state setter should be used:

updateFlash((flashval)=> {
    if(flashval=="&nbsp|" ) {
        return "<span>&nbsp&nbsp</span>";
    } else {
        return "&nbsp|";
    }
})

This avoids the possible usage of stale state values.

https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

The Flasher component should be function Flasher(props).
If you need to initialize the state using props (flashval), use the getDerivedStateFromProps method, as it takes into account any changes in props.

If you are new to React, my personal advice is to learn React using class components from reactjs.org itself. Use hooks only if you are strong in the basics.

  • Related