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>  </span>")
useEffect(() => {
if (flash) {
setTimeout(() => {
if (flashval === " |") {
updateFlash("<span>  </span>")
} else {
updateFlash(" |")
}
}, 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.
- The first parameter is missed for
Object.assign
:
updateTxt(Object.assign({}, txt));
- 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]);
- There is a bug. When index is changed this line
txt.current_text.length 1
works incorrectly. You can resetcurrent_text
:
txt.index ;
txt.current_text = "";
And move this part down:
txt.current_text = current_full_text.substring(
0,
txt.current_text.length 1
);
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==" |" ) {
return "<span>  </span>";
} else {
return " |";
}
})
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.