0

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?

1
  • All three answers work. So which one to choose?
    – Eli Tref
    Commented Jan 3, 2023 at 9:04

3 Answers 3

1

Try useCallback

const inputRef = useCallback(node => {
    if (node) {
      node.focus();
      node.setSelectionRange(node.value.length, node.value.length);
    }
  }, []);
1
  • Nice idea! Never thought about using a callback ref for this
    – adsy
    Commented Jan 7, 2023 at 16:31
1

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
/>
1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.