Home > OS >  I can't set focus on the input
I can't set focus on the input

Time:01-03

I have this component https://stackblitz.com/edit/react-ts-u7rw2w?file=App.tsx

When I click and call toggleInputCardName, I see in the console "NO inputRef.current null false". At the same time, an input is rendered on the page and above it I see "null inputRef true".

I tried using useEffect, but the result is the same.

How to understand this? IsInputCardName is simultaneously true and false? How to set focus on the input in toggleInputCardName?

CodePudding user response:

It is because the setState is async. I think about 2 possibilities.

First one :

  const toggleInputCardName = () => {
    setInputCardNameVisibity(!isInputCardName);
  };

  React.useEffect(() => {
    if (inputRef.current) {
      console.log('YES inputRef.current', inputRef.current);
      inputRef.current.focus();
      inputRef.current.select();
    } else {
      console.log('NO inputRef.current', inputRef.current, isInputCardName);
    }
  }, [isInputCardName]);

Second one, you could simply add autofocus on the input and don't use ref :

<input
  className="input-card-title"
  type="text"
  value="value"
  autoFocus
/>

CodePudding user response:

You need useLayoutEffect:

  const toggleInputCardName = () => {
    setInputCardNameVisibity(!isInputCardName)
  }

  React.useLayoutEffect(() => {
    if (!inputRef.current) return
    inputRef.current.focus()
    inputRef.current.select()
  }, [isInputCardName])

The reason it doesn't work in the handler or in a plain useEffect is that they are executing before the input is actually written to the DOM. State changes in react are flushed later, and don't happen immediately. useLayoutEffect waits until the DOM change is committed.

Another way of doing it is by wrapping it in a 0 second timeout. This looks hackier than it is, as a timer with 0 as the time to wait, will just wait until the current call stack is finished before executing.

  const toggleInputCardName = () => {
    setInputCardNameVisibity(!isInputCardName)
    setTimeout(() => {
      if (inputRef.current) {
        console.log('YES inputRef.current', inputRef.current)
        inputRef.current.focus()
        inputRef.current.select()
      } else {
        console.log('NO inputRef.current', inputRef.current, isInputCardName)
      }
    }, 0)
  }

But I'd probably useLayoutEffect.

@OneQ answer of using the focus attribute also makes a lot of sense and reduces noise.

  • Related