Before introducing Refs, let’s look at two concepts: managed components and uncontrolled components.

The controlled components

In HTML, form elements such as input, Textarea, SELECT can often maintain their own state and be updated based on user input. In React, mutable state is usually stored in the component’s state property and can only be updated by setState(). Here, we use the React state as the only data source to render the React component of the form to control how the form is sent during user input. The “form input element whose value is controlled this way by React” is called a controlled component.

Uncontrolled component

Components that are not controlled by the React component. In the controlled component, the form data is processed by the React component. The alternative is an uncontrolled component, where the form data is processed by the DOM itself. A file input tag is a typical uncontrolled component whose value can only be set by the user and obtained through features provided by the DOM itself.

The biggest difference between a controlled component and an uncontrolled component is that the former maintains its own state value change, which can be combined with its own change event to easily modify or verify user input.

In React, Refs make it much easier to maintain the state values of components that are out of your control. Let’s focus on Refs.

What is the Refs

Refs is a tool to get instances of DOM nodes or React elements. Refs in React provides a way for users to access DOM nodes or React elements created in the Render method.

In a React singleton data flow, props is the only way for parent and child components to interact. To modify a child component, you need to re-render it with new props. However, in some cases, you need to force changes to child components outside of the data flow. The child component being modified may be an instance of the React component or a DOM element. React provides concrete solutions for both cases through the use of Refs.

Usage scenarios

Refs is usually suitable for use in the following scenarios:

  1. Control of DOM element focus, content selection, or media playback;
  2. Control DOM elements to trigger animation effects;
  3. Integration with third-party DOM libraries.

Avoid using Refs to do anything that can be done with declarative implementations. For example, to avoid exposing open(), show(), hide(), and close() methods inside Dialog, Loading, and Alert components, it is better to control them through isXX properties.

use

There are two ways to use refs: 1) through the React. CreateRef () API (introduced after React 16.3); 2) In earlier versions, we recommended refs in the form of callbacks.

React.createRef()

class TestComp extends React.Component {
  constructor(props) {
    super(props);
    this.tRef = React.createRef();
  }
  render() {
    return (
      <div ref={ this.tRef} ></div>)}}Copy the code

The above code creates an instance attribute this.tref and passes it to the DOM element div. Subsequent references to this node can then be accessed in the ref’s current property. The value of ref varies depending on the node type:

  1. When the ref attribute is used for normal HTML elements, the ref created with react.createref () in the constructor receives the underlying DOM element as its current attribute.
class TestComp extends React.Component {
  constructor(props) {
    super(props);
    // Create a ref to store the DOM element input
    this.textInput = React.createRef();
    this.focusEvent = this.focusEvent.bind(this);
  }
  focusEvent() {
    // Access the input box directly through the native API to get the focus event
    this.textInput.current.focus();
  }
  render() {
    return(<div> <input type="text" ref={this.textInput} /> <input type="button" value=" gettextbox focusEvent "onClick={this.focusEvent}/> </div> ); }}Copy the code
  1. When the REF attribute is used for a custom class component, the REF object receives the component’s mounted instance as its current attribute.
class ParentComp extends React.Component {
  constructor(props) {
    super(props);
    // Create ref to point to the ChildrenComp component instance
    this.textInput = React.createRef();
  }

  componentDidMount() {
    // Call the child focusTextInput method to trigger the child's internal text box to get the focus event
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <ChildrenComp ref={ this.textInput} / >); }}Copy the code

class ChildrenComp extends React.Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }
  focusTextInput() {
    this.inputRef.current.focus();
  }
  render(){
    return(
      <div>
        <input type='text' value='The parent controls getting the focus event through the focusTextInput() method' ref={ this.inputRef} / >
      </div>)}}Copy the code
  1. You cannot use ref attributes on function components because they do not have instances.
The callback Refs

React also supports another way to use refs called “callback Refs,” which gives us more precise control over when refs are set and removed. The React component instance or HTML DOM element is accepted as an argument in this callback function so that they can be stored and accessed elsewhere.


class TestComp extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = null;
    // Bind a reference to the DOM node of the text input box to the React instance this.textinput using the callback function of 'ref'
    this.inputRef = element= > {
      this.textInput = element;
    }
    this.focus = (a)= > {
      if (this.textInput) {
        this.textInput.focus();
      }
    }
  }
  componentDidMount() {
    this.focus();
  }
  render() {
    return (
      <div>
        <input type='text' ref={ this.inputRef} / >
      </div>); }}Copy the code

React will call the REF callback and pass in the DOM element when the component is mounted and null when it is unmounted. React ensures that the refs are up to date before componentDidMount or componentDidUpdate is triggered. In a class component, usually the parent component passes its refs callback function to the child component as props, and the child component passes the same function as a special ref attribute to the corresponding DOM element.

If the ref callback is defined as an inline function, it is executed twice during the update process, passing in the argument NULL the first time, and then the argument DOM element the second time. This is because a new function instance is created each time it renders, so React clears the old ref and sets the new one. This can be avoided by defining the ref callback function as a class binding function, but in most cases it is irrelevant.

class TestComp extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = null;
    // Initialize flag to init
    this.state = {
      flag: 'init'
    }
    this.focus = (a)= > {
      if (this.textInput) {
        this.textInput.focus();
      }
    }
  }
  componentDidMount() {
    this.focus();
    // After render is executed for the first time, the status flag is update
    this.setState({
      flag: 'update'
    });
  }
  render() {
    return (
      <div>{/* Define ref */} via inline callback<input type='text' value={this.state.flag} ref={(element)= >{ console.log('element', element); // Outputs the passed Element to the console this.textInput = Element; }} / ></div>)}}Copy the code

Obsolete API: Refs of type String

If you are currently accessing refs using this.refs.textInput, the official recommendation is to use a callback or createRef API instead.

How do I expose the DOM to the parent component via Refs

In rare cases, we might want to refer to the DOM node of the child node in the parent component (this is officially not recommended because it breaks the component’s wrapper), and the user triggers focus or measures the size or position of the child DOM node. Although we can solve this problem by adding a REF to a child component, this is not an ideal solution because we can only get component instances, not DOM nodes. It also does not work on function components.

In React 16.3 and later, we recommend using ref forwarding to do this.

Ref forwarding enables a component to expose its child’s Ref as well as its own.

Ref forwarding is a technique for automatically passing a ref through a component to one of its children. This is typically not necessary for most components in the application. However, it can be useful for some kinds of components, especially in reusable component libraries.

Ref Forwarding is a technique for automatically passing a Ref through a component to its children. Let’s use a specific case to demonstrate the effect.

const ref = React.createRef();
const BtnComp = React.forwardRef((props, ref) = > {
  return (
    <div>
      <button ref={ref} className='btn'>
        { props.children }
      </button>
    </div>)});class TestComp extends React.Component {
  clickEvent() {
    if (ref && ref.current) {
      ref.current.addEventListener('click', () = > {console.log('hello click! ')}); } } componentDidMount() {console.log('The current button's class is:', ref.current.className); // btn
    this.clickEvent(); // hello click!
  }
  render() {
    return (
      <div>
        <BtnComp ref={ref}>Click on the I</BtnComp>
      </div>); }}Copy the code

In the example above, the component used, BtnComp, can take a reference to the underlying Button DOM node and manipulate it if necessary, just as a normal HTML element, Button, uses the DOM directly.

Matters needing attention

The second ref parameter exists only when the react. forwardRef callback is used to define the component. Regular functions or class components do not receive ref arguments, and refs are not supplied in props.

Ref forwarding is not limited to DOM components. You can also forward refs to class component instances.

Refs in higher-order components

HOC is an advanced technique used in React to reuse component logic. HOC is a function that retrieves a component and returns a new one. Let’s take a look at a specific example of how refs can be used in higher-order component clocks.


// Record the status change operation
function logProps(Comp) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:'.this.props);
    }
    render() {
      const{ forwardedRef, ... rest } =this.props;
      return<Comp ref={ forwardedRef } {... rest} />; } } return React.forwardRef((props, ref) => { return <LogProps { ... props } forwardedRef={ ref } />; }); } // subcomponent const BtnComp = React. ref) => { return ( <div> <button ref={ref} className='btn'> { props.children } </button> </div> ) }); Const NewBtnComp = logProps(BtnComp); const NewBtnComp = logProps(BtnComp); class TestComp extends React.Component { constructor(props) { super(props); this.btnRef = React.createRef(); This.state = {value: 'init'}} componentDidMount() {console.log('ref', this.btnref); console.log('ref', this.btnRef.current.className); this.btnRef.current.classList.add('cancel'); / / the button to add a class in BtnComp enclosing btnRef. Current. The focus (); SetTimeout (() => {this.setState({value: 'update'}); }, 10000); } render() { return ( <NewBtnComp ref={this.btnRef}>{this.state.value}</NewBtnComp> ); }}Copy the code

The final effect is as follows:


Note: This article is from the public account react_native and has been reprinted with the author’s permission.