Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react ref with focus() doesn't work without setTimeout (my example)

Tags:

reactjs

I have encounter this problem, the .focus() only works with setTimeout if i take it out and it stop working. can anyone explain me what's the reason for that, possible i am doing it incorrectly and how can i fix the problem.

    componentDidMount() {
        React.findDOMNode(this.refs.titleInput).getElementsByTagName('input')[0].focus();
    }

works example with setTimeout

componentDidMount() {
    setTimeout(() => {
        React.findDOMNode(this.refs.titleInput).getElementsByTagName('input')[0].focus();
    }, 1);
}

JXS

<input ref="titleInput" type="text" />

and i have followed this example React set focus on input after render

render function

render() {
        const {title, description, tagtext, siteName} = (this.state.selected !== undefined) ? this.state.selected : {};
        const hasContentChangedYet = this.hasContentChangedYet(title, description);

        return (
            <div>
                <h2 className={styles.formMainHeader}>Edit Meta-Data Form</h2>
                <table className={styles.formBlock}>
                    <tbody>
                    <tr>
                        <td className={styles.tagEditLabel}>
                            Tag
                        </td>
                        <td className={styles.inputFieldDisableContainer}>
                            {tagtext}
                        </td>
                    </tr>
                    <tr>
                        <td className={styles.tagEditLabel}>
                            Site
                        </td>
                        <td className={styles.inputFieldDisableContainer}>
                            {siteName}
                        </td>
                    </tr>
                    <tr>
                        <td className={styles.tagEditLabel}>
                            Title
                        </td>
                        <td className={styles.inputFieldContainer}>
                            <ReactInputField
                                ref="titleInput"
                                id="title"
                                defaultValue={(title) ? title : ''}
                                onChange={this.onInputChange}
                                placeholder="Title"
                                clearTool={true} />
                        </td>
                    </tr>
                    <tr>
                        <td className={styles.tagEditLabel}>
                            Description
                        </td>
                        <td className={styles.inputFieldContainer}>
                            <ReactInputField
                                id="description"
                                defaultValue={(description) ? description : ''}
                                onChange={this.onInputChange}
                                placeholder="Description"
                                clearTool={true} />
                        </td>
                    </tr>
                    </tbody>
                </table>

                <div className={styles.formFooter}>
                    <button id="save-button" className={styles.saveButton} disabled={!hasContentChangedYet} onClick={() => this.handleSavePressed()}>
                        Save
                    </button>
                    <button id="form-cancel-button" className={styles.cancelButton} onClick={this.actions.form.cancelUpdateToTagData}>
                        Cancel
                    </button>

                </div>
            </div>
        );
    }
like image 605
Bill Avatar asked Feb 20 '16 10:02

Bill


People also ask

Why is my ref null React?

A React ref most commonly returns undefined or null when we try to access its current property before its corresponding DOM element is rendered. To get around this, access the ref in the useEffect hook or when an event is triggered.

Why we should not use REF IN React?

It is a general rule of thumb to avoid using refs unless you absolutely have to. The official React documentation outlined only three possible use cases where refs are entirely considered useful for lack of better alternatives: Managing focus, text selection, or media playback. Triggering imperative animations.


2 Answers

After seeing the update to the question, I realise that you have deeply nested HTML passed to the render function, and the input element of your interest will indeed not be available at the time of the componentDidMount call on the ancestor element. As stated in the React v0.13 Change Log:

ref resolution order has changed slightly such that a ref to a component is available immediately after its componentDidMount method is called; this change should be observable only if your component calls a parent component's callback within your componentDidMount, which is an anti-pattern and should be avoided regardless

This is your case. So either you have to break down the HTML structure into separately rendered elements, as described here, and then you would access the input element in its own componentDidMount callback; or you just stick with the timer hack you have.

Use of componentDidMount makes sure the code runs only when the component on which it is called is mounted (see quote from docs further down).

Note that calling React.findDOMNode is discouraged:

In most cases, you can attach a ref to the DOM node and avoid using findDOMNode at all.

Note

findDOMNode() is an escape hatch used to access the underlying DOM node. In most cases, use of this escape hatch is discouraged because it pierces the component abstraction.

findDOMNode() only works on mounted components (that is, components that have been placed in the DOM). If you try to call this on a component that has not been mounted yet (like calling findDOMNode() in render() on a component that has yet to be created) an exception will be thrown.

And from the docs on the ref string attribute:

  1. Assign a ref attribute to anything returned from render such as:

     <input ref="myInput" />
    
  2. In some other code (typically event handler code), access the backing instance via this.refs as in:

    var input = this.refs.myInput;  
    var inputValue = input.value;  
    var inputRect = input.getBoundingClientRect();  
    

Alternatively, you could eliminate the need of the code, and use the JSX autoFocus attribute:

<ReactInputField
        ref="titleInput"
        autoFocus
        ... />
like image 113
trincot Avatar answered Oct 04 '22 10:10

trincot


Using setTimeout() is a bad idea and using componentDidMount() is irrelevant. You may find the answer to your question in the following example:

In a parent component I render a primereact Dialog with an InputText in it:

<Dialog visible={this.state.visible} ...>
  <InputText ref={(nameInp) => {this.nameInp = nameInp}} .../>
  ...
</Dialog>

Initially, this.state.visible is false and the Dialog is hidden. To show the Dialog, I re-render the parent component by calling showDlg(), where nameInp is the ref to InputText:

showDlg() {
  this.setState({visible:true}, ()=>{
    this.nameInp.element.focus();
  });
}

The input element gets the focus only after rendering has been accomplished and the setState callback function called.

Instead of using the setState callback, in some cases you may simply use:

componentDidUpdate(){
  this.nameInp.element.focus();
}

However, componentDidUpdate() is being called every time you (re)render the component, including in case the InputText is hidden.

See also: https://reactjs.org/docs/react-component.html#setstate

like image 33
vess Avatar answered Oct 04 '22 11:10

vess