useLayoutEffect vs useEffect (with a setTimeout)

I am conditionally rendering a list of input elements. I want to give the first element focus when they are rendered. I added refs to the inputs and a useEffect where I focus on the respective ref (when the change happens). Adding refs to the input -
// inside the map
<input
ref={(el) => {optionRefs.current[index] = el;}}
/>
// inside the map
<input
ref={(el) => {optionRefs.current[index] = el;}}
/>
My useEffect -
useEffect(() => {
if (
(type === "select" || type === "multi-select") &&
optionRefs.current[0]
) {
optionRefs.current[0]?.focus();
}
}, [type]);
useEffect(() => {
if (
(type === "select" || type === "multi-select") &&
optionRefs.current[0]
) {
optionRefs.current[0]?.focus();
}
}, [type]);
However, this doesn't work because the input component isn't rendered when it is trying to focus on the ref. GPT recommended I add a setTimeout -
useEffect(() => {
if (
(type === "select" || type === "multi-select") &&
optionRefs.current[0]
) {
setTimeout(() => {
optionRefs.current[0]?.focus();
}, 0);
}
}, [type]);
useEffect(() => {
if (
(type === "select" || type === "multi-select") &&
optionRefs.current[0]
) {
setTimeout(() => {
optionRefs.current[0]?.focus();
}, 0);
}
}, [type]);
And this works! This isn't best practice though so I looked at what is and it seems to be useLayoutEffect. When I tried the first approach with it, I wasn't successful. What am I doing wrong here?
2 Replies
michaeldrotar
michaeldrotar6mo ago
setTimeout runs after the frame paints, so it'd technically affect the next frame and the delay can be noticeable requestAnimationFrame runs before it paints - I usually use this when struggling with useLayoutEffect and it would be a fine option for your case if you want to figure out why useLayoutEffect isn't working though, it'd be helpful to reproduce the issue somewhere like StackBlitz. There's no cut and dry answer for it. I could guess that perhaps optionRefs.current[0] is defined yet but then it'd be a dozen questions to figure out why it's not defined since I have limited context. What you did provide certainly seems reasonable enough that it should work.
Juraj98
Juraj986mo ago
@jairrard, have you tried setting autoFocus={true} on the first input?
Want results from more Discord servers?
Add your server