18

In React, with classes I can set the focus to an input when the component loads, something like this:

class Foo extends React.Component {
    txt1 = null;

    componentDidMount() {
        this.txt1.focus();
    }

    render() {
        return (
            <input type="text"
                ref={e => this.txt1 = e}/>
        );
    }
}

I'm trying to rewrite this component using the new hooks proposal.

I suppose I should use useEffect instead of componentDidMount, but how can I rewrite the focus logic?

rodrigocfd
  • 6,450
  • 6
  • 34
  • 68
  • Just wanted to mention, that it is also possible to set the autofocus property on the input itself without the need of JS like: ``. I know this is not a direct answer to your question, that's why I put it in a comment and I don't want to invalidate the given answer, I would do it the same way. I just think in many cases it's enough to just set the property directly and be done with it. – Stefan Ladwig Mar 28 '19 at 08:34

2 Answers2

34

You can use the useRef hook to create a ref, and then focus it in a useEffect hook with an empty array as second argument to make sure it is only run after the initial render.

const { useRef, useEffect } = React;

function Foo() {
  const txt1 = useRef(null);

  useEffect(() => {
    txt1.current.focus();
  }, []);

  return <input type="text" ref={txt1} />;
}

ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>
Tholle
  • 108,070
  • 19
  • 198
  • 189
  • Why isn't `txt1` `null` when it's first called? – notgiorgi Mar 22 '19 at 08:32
  • 2
    @notgiorgi The argument given to `useRef` is what value the ref's `current` property should be, so the initial value is `{ current: null }`. Giving an empty array as second argument to `useEffect` makes it so the effect is only run once after the initial render, which is what OP wanted. – Tholle Mar 22 '19 at 08:34
  • 2
    Ah, thanks, I forgot that `useEffect` runs _after_ the initial render not _before_ – notgiorgi Mar 22 '19 at 08:37
  • This will throw a warning when using eslint "React Hook useEffect has a missing dependency: 'text1'. Either include it or remove the dependency array.eslintreact-hooks/exhaustive-deps" – soulsako20 Jan 11 '23 at 13:58
0

According to official docs: https://reactjs.org/docs/hooks-reference.html#useref you can use useRef.

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

A common use case is to access a child imperatively:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

Essentially, useRef is like a “box” that can hold a mutable value in its .current property.

You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with <div ref={myRef} />, React will set its .current property to the corresponding DOM node whenever that node changes.

However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.

Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.

Ruslan Korkin
  • 3,973
  • 1
  • 27
  • 23