setInterval loops over the first character

What was I trying to do? Trying to display the page brand name one letter at a time, on page render.. What have you tried already to fix it? What I tried is in no way logical, lazy fixed it by repeating the character index it kept omitting Errors: none , just skipping (omission) Overlook any inadequacy, I am still learning 😿 function Brand({text, speed}) { const [brandName, setBrandName] = useState(""); const indexRef = useRef(0); useEffect(() => { setBrandName(""); indexRef.current = 0; console.log(indexRef); const intervalId = setInterval(() => { console.log('Before update:', brandName, indexRef.current); // Debugging log setBrandName((prev) => { console.log('Updating:', prev, text.charAt(indexRef.current)); // Debugging log return prev + text.charAt(indexRef.current); }); indexRef.current++; console.log('After update:', brandName, indexRef.current); // Debugging log if (indexRef.current >= text.length) { clearInterval(intervalId); } }, speed); return () => clearInterval(intervalId); },[text, speed, brandName]); return ( <span className='font-bold text-3xl'>{brandName}</span> ) } export default function Logo() { return ( <Link to="/"> <div className='flex items-center gap-2'> <img src="avatar.jpg" alt="logo" className='w-12 h-12 rounded-full'/> <Brand text='MurWebsite' speed={400} /> </div> </Link> ) }
11 Replies
ChooKing
ChooKing3w ago
The problem is the dependency array. Remove brandName from it. The useEffect changes this value and it's a dependency, so you get infnite re-renders.
Ubong Josh
Ubong Josh3w ago
EsLint screams at me when I take it out of the dependency, and it doesn't make any difference if it's there or not
Aoi
Aoi3w ago
export function Brand({ name, speed }) {
let [currentLength, setCurrentLength] = useState();

useEffect(() => {
let interval = setInterval(() => {
setCurrentLength((currentLength) => {
if (currentLength >= name.length) return 0;
return currentLength + 1;
});
}, speed);

return () => {
clearInterval(interval);
};
}, [speed]);

return <span>{name.splice(0, currentLength)}</span>;
}
export function Brand({ name, speed }) {
let [currentLength, setCurrentLength] = useState();

useEffect(() => {
let interval = setInterval(() => {
setCurrentLength((currentLength) => {
if (currentLength >= name.length) return 0;
return currentLength + 1;
});
}, speed);

return () => {
clearInterval(interval);
};
}, [speed]);

return <span>{name.splice(0, currentLength)}</span>;
}
Please don't use useRef to store a state, even if it's 'temporary'. Some advice based on the code you wrote: 1. Find the smallest state(s) that is changing in your component. (for your case it will be the length of the word, not the word itself) 2. Check if other things can be derived from those states. (in your case, the part of the word to display can be calculated from the length) Also: general advice is to never put the state you are updating in your useEffect as a dependency of that useEffect. (ofc there are some exceptions)
ChooKing
ChooKing3w ago
I am not sure if you are misunderstanding what I said. Here is code that I tested and it works without any warning or errors:
import { useState, useEffect, useRef } from 'react'

export default function Brand({text, speed}) {
const [brandName, setBrandName] = useState("");
const indexRef = useRef(0);

useEffect(() => {
setBrandName("");
indexRef.current = 0;
console.log(indexRef);
const intervalId = setInterval(() => {
console.log('Before update:', brandName, indexRef.current); // Debugging log
setBrandName((prev) => {
console.log('Updating:', prev, text.charAt(indexRef.current)); // Debugging log
return prev + text.charAt(indexRef.current);
});
indexRef.current++;
console.log('After update:', brandName, indexRef.current); // Debugging log
if (indexRef.current >= text.length) {
clearInterval(intervalId);
}
}, speed);

return () => clearInterval(intervalId);

},[text, speed]);//Only change is here.

return (
<span className='font-bold text-3xl'>{brandName}</span>
)
}
import { useState, useEffect, useRef } from 'react'

export default function Brand({text, speed}) {
const [brandName, setBrandName] = useState("");
const indexRef = useRef(0);

useEffect(() => {
setBrandName("");
indexRef.current = 0;
console.log(indexRef);
const intervalId = setInterval(() => {
console.log('Before update:', brandName, indexRef.current); // Debugging log
setBrandName((prev) => {
console.log('Updating:', prev, text.charAt(indexRef.current)); // Debugging log
return prev + text.charAt(indexRef.current);
});
indexRef.current++;
console.log('After update:', brandName, indexRef.current); // Debugging log
if (indexRef.current >= text.length) {
clearInterval(intervalId);
}
}, speed);

return () => clearInterval(intervalId);

},[text, speed]);//Only change is here.

return (
<span className='font-bold text-3xl'>{brandName}</span>
)
}
Note that I also tested without removing that item from the dependency array and it did not work. I did not use eslint on my test project. If the only reason you are writing wrong code is to obey what eslint is telling you, then you either need to modify the eslint configuration or stop using it.
Ubong Josh
Ubong Josh3w ago
Yes , I did this I'll try this I am still learning 😹 if I am to remove it completely I'll have to look up some syntax , or type everything manually which is a stress
ChooKing
ChooKing3w ago
Remove what completely? I think you are still misunderstanding. I removed only one comma and one variable name. You don't need to look up anything. My code is an exact copy and paste of your code with one very small change. I just realized you are referring to the eslint.
Ubong Josh
Ubong Josh3w ago
Is the initial useState not supposed to be 0? , and if when the current length is greater than name length, why not return nothing 🌝 it feels like return 0 would just kickstart the whole loop again I'll check it now, anyways, I'll drop feedback right after Sorry, I meant..
ChooKing
ChooKing3w ago
I am not sure if you understand what Aoi said here: "Also: general advice is to never put the state you are updating in your useEffect as a dependency of that useEffect. (ofc there are some exceptions)" It is exactly the same thing that I said but in different words. It is a widely known common bug in beginner's code when learning to use useEffect.
Ubong Josh
Ubong Josh3w ago
Ok I've seen this Ok thanks a lot, I'll keep that in mind I really didn't put it at first.. the underline only went away after I put it Like you said, I need to make some changes to my EsLint settings
export function Brand({ text, speed }) {
let [currentLength, setCurrentLength] = useState(0);

useEffect(() => {
let interval = setInterval(() => {
setCurrentLength((currentLength) => {
if (currentLength >= text.length) return currentLength;
return currentLength + 1;
});
}, speed);

return () => {
clearInterval(interval);
};
}, [text, speed]);

return <span className='font-bold text-3xl'> {text.slice(0, currentLength)} </span> ;
}
export function Brand({ text, speed }) {
let [currentLength, setCurrentLength] = useState(0);

useEffect(() => {
let interval = setInterval(() => {
setCurrentLength((currentLength) => {
if (currentLength >= text.length) return currentLength;
return currentLength + 1;
});
}, speed);

return () => {
clearInterval(interval);
};
}, [text, speed]);

return <span className='font-bold text-3xl'> {text.slice(0, currentLength)} </span> ;
}
this worked out, thanks a lot @ChooKing @Aoi
Aoi
Aoi3w ago
Great, btw you should also clear the interval when the currentLength >= text.length.
Ubong Josh
Ubong Josh2w ago
Ok, that is much better 👌 thanks