Refs provides a way to access DOM nodes or React elements created in the Render method.

More articles can be read: github.com/YvetteLau/B…

Refs Usage scenario

In some cases, we need to force changes to subcomponents outside of the typical data flow. The subcomponent may be an instance of the React component or a DOM element, for example:

  • Manage focus, text selection or media playback.
  • Trigger the forced animation.
  • Integrate third-party DOM libraries.

Set the Refs

1. createRef

Supports use within function components and class components

CreateRef was introduced in Release 16.3 of Act.

Create the Refs

Create Refs with react.createref () and attach them to the React element via the ref attribute. Typically in constructors, Refs are assigned to instance properties for reference throughout the component.

Access to the Refs

When a ref is passed to an element in render, a reference to that node can be accessed in the ref’s current property.

import React from 'react';
export default class MyInput extends React.Component {
    constructor(props) {
        super(props);
        // Assign to instance attributes
        this.inputRef = React.createRef(null);
    }

    componentDidMount() {
        // Get a reference to this node through this.inputref.current
        this.inputRef && this.inputRef.current.focus();
    }

    render() {
        // associate  ref with the 'inputRef' created in the constructor
        return (
            <input type="text" ref={this.inputRef}/>)}}Copy the code

The value of ref varies depending on the type of node:

  • whenrefProperty is used toHTMLElement is used in the constructorReact.createRef()To create therefReceive the underlyingDOMElement as itscurrentProperties.
  • whenrefProperty for a custom class component,refObject receives a mount instance of the component as itcurrentProperties.
  • Cannot be used on function componentsrefProperty because the function component has no instance.

Conclusion: By adding a ref to the DOM, we can get a reference to the DOM node through ref. We can’t use the ref attribute on function components because function components have no instance.

2. useRef

Use only within function components

UseRef was introduced in Act 16.8 and can only be used in function components.

Create the Refs

Create Refs with React.useref () and attach them to the React element via the ref attribute.

const refContainer = useRef(initialValue);
Copy the code

The ref object returned by useRef remains constant throughout the life of the component.

Access to the Refs

When a ref is passed to the React element, a reference to that node can be accessed in the current attribute of the ref.

import React from 'react';

export default function MyInput(props) {
    const inputRef = React.useRef(null);
    React.useEffect(() = > {
        inputRef.current.focus();
    });
    return (
        <input type="text" ref={inputRef} />)}Copy the code

The ref object returned by react.useref () remains the same for the lifetime of the component. Compare this with the react.createref () :

import React, { useRef, useEffect, createRef, useState } from 'react';
function MyInput() {
    let [count, setCount] = useState(0);

    const myRef = createRef(null);
    const inputRef = useRef(null);
    // Execute only once
    useEffect(() = > {
        inputRef.current.focus();
        window.myRef = myRef;
        window.inputRef = inputRef; } []); useEffect(() = > {
        // createRef is false every time except the first time
        console.log('myRef === window.myRef', myRef === window.myRef);
        // always true [useRef]
        console.log('inputRef === window.inputRef', inputRef === window.inputRef);
    })
    return (
        <>
            <input type="text" ref={inputRef}/>
            <button onClick={()= > setCount(count+1)}>{count}</button>
        </>)}Copy the code

3. The callback Refs

Supports use within function components and class components

React supports setting refs by calling refs back. This gives us more fine-grained control over when Refs are set and released.

To use callback refs, you need to pass the callback function data to the REF attribute of the React element. This function takes a React component instance or AN HTML DOM element as an argument and mounts it to the instance property, as shown below:

import React from 'react';

export default class MyInput extends React.Component {
    constructor(props) {
        super(props);
        this.inputRef = null;
        this.setTextInputRef = (ele) = > {
            this.inputRef = ele; }}componentDidMount() {
        this.inputRef && this.inputRef.focus();
    }
    render() {
        return (
            <input type="text" ref={this.setTextInputRef}/>)}}Copy the code

React calls the REF callback and passes in the DOM element (or React instance) when the component is mounted, and calls it and passes in NULL when it is unmounted. React ensures that the Refs are up to date before componentDidMount or componentDidUpdate is triggered.

Refs in the form of callbacks can be passed between components.

import React from 'react';

export default function Form() {
    let ref = null;
    React.useEffect(() = > {
        //ref is the input node in MyInput
        ref.focus();
    }, [ref]);

    return (
        <>
            <MyInput inputRef={ele= > ref = ele} />
            {/** other code */}

        </>)}function MyInput (props) {
    return (
        <input type="text" ref={props.inputRef}/>)}Copy the code

4. String Refs (Obsolete API)

Function components don’t support using string refs [support createRef | useRef | callback Ref]

function MyInput() {
    return (
        <>
            <input type='text' ref={'inputRef'} / >
        </>)}Copy the code

Class components

Get the React element from this.refs.XXX.

class MyInput extends React.Component {
    componentDidMount() {
        this.refs.inputRef.focus();
    }
    render() {
        return (
            <input type='text' ref={'inputRef'} / >)}}Copy the code

Ref to pass

Before Hook, HOC and render props were the primary means of reusing component logic in React.

Although the convention for higher-order components is to pass all props to the wrapped component, refs are not passed. In fact, refs are not a prop; like keys, they are handled by React.

This can be fixed with the React. ForwardRef (new in React 16.3). Before the react. forwardRef, we can add the forwardedRef to the container component.

The React. ForwardRef before

import React from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics';

const withData = (WrappedComponent) = > {
    class ProxyComponent extends React.Component {
        componentDidMount() {
            //code
        }
        // One caveat here is that we need to know that this component is a wrapped component
        // Pass the ref value to the forwardedRef prop
        render() {
            const{forwardedRef, ... remainingProps} =this.props;
            return (
                <WrappedComponent ref={forwardedRef} {. remainingProps} / >)}}// Specify displayName. No static method is copied (the point is not HOC)
    ProxyComponent.displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
    // Copy non-react static methods
    hoistNonReactStatic(ProxyComponent, WrappedComponent);
    return ProxyComponent;
}
Copy the code

In this example, we pass the ref property value to the wrapped component via the forwardedRef prop using:

class MyInput extends React.Component {
    render() {
        return (
            <input type="text" {. this.props} / >
        )
    }
}

MyInput = withData(MyInput);
function Form(props) {
    const inputRef = React.useRef(null);
    React.useEffect(() = > {
        console.log(inputRef.current)
    })
    // When we use MyInput, we need to distinguish whether it is a wrapped component to specify ref or forwardedRef
    return (
        <MyInput forwardedRef={inputRef} />)}Copy the code

React.forwardRef

Ref forwarding is a technique for automatically passing a Ref through a component to one of its children, allowing some components to receive a Ref and pass it down to the children.

Forward ref to DOM:

import React from 'react';

const MyInput = React.forwardRef((props, ref) = > {
    return (
        <input type="text" ref={ref} {. props} / >)});function Form() {
    const inputRef = React.useRef(null);
    React.useEffect(() = > {
        console.log(inputRef.current);/ / input node
    })
    return (
        <MyInput ref={inputRef} />)}Copy the code
  1. callReact.useRefCreated aReact refAnd assign it torefThe variable.
  2. The specifiedrefIs a JSX property and is passed down<MyInput ref={inputRef}>
  3. The React to passrefforwardRefWithin the function(props, ref) => ...As its second parameter.
  4. Forward this downrefParameters to<button ref={ref}>, specifying it as a JSX property
  5. whenrefMount complete,inputRef.currentPoint to theinputDOM node

Pay attention to

The second argument, ref, only exists when the react. forwardRef component is defined. Regular functions and class components do not accept ref arguments, and ref does not exist in props.

Before the react. forwardRef, if we want to pass the ref attribute to the subcomponent, we need to distinguish whether it is a component wrapped by HOC, which causes some inconvenience to use. Let’s refactor using the React. ForwardRef.

import React from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics';

function withData(WrappedComponent) {
    class ProxyComponent extends React.Component {
        componentDidMount() {
            //code
        }
        render() {
            const{forwardedRef, ... remainingProps} =this.props;
            return (
                <WrappedComponent ref={forwardedRef} {. remainingProps} / >)}}// when we use a component wrapped withData, we just need to pass the ref
    const forwardRef = React.forwardRef((props, ref) = > (
        <ProxyComponent {. props} forwardedRef={ref} />
    ));
    / / specify the displayName.
    forwardRef.displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
    return hoistNonReactStatic(forwardRef, WrappedComponent);
}

Copy the code
class MyInput extends React.Component {
    render() {
        return (
            <input type="text" {. this.props} / >
        )
    }
}
MyInput.getName = function() {
    console.log('name');
}
MyInput = withData(MyInput);
console.log(MyInput.getName); // Test whether the static method copy is normal


function Form(props) {
    const inputRef = React.useRef(null);
    React.useEffect(() = > {
        console.log(inputRef.current);// The wrapped component MyInput
    })
    // When used, just pass ref
    return (
        <MyInput ref={inputRef} />)}Copy the code

Get instances of child components (wrapped puppet components) in React-redux

In older versions (V4 / V5)

We know that connect has four parameters, and if we want the instance of the subcomponent (puppet component) in the parent component, we need to set the withRef of the fourth parameter options to true. An instance of the puppet component (wrapped component) can then be obtained in the parent component using the getWrappedInstance() method of the container component instance, as shown below:

//MyInput.js
import React from 'react';
import { connect } from 'react-redux';

class MyInput extends React.Component {
    render() {
        return (
            <input type="text" />)}}export default connect(null.null.null, { withRef: true })(MyInput);
Copy the code
//index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import MyInput from './MyInput';

function reducer(state, action) {
    return state;
}
const store = createStore(reducer);

function Main() {
    let ref = React.createRef();
    React.useEffect(() = > {
        console.log(ref.current.getWrappedInstance());
    })
    return (
        <Provider store={store}>
            <MyInput ref={ref} />
        </Provider>
    )
}

ReactDOM.render(<Main />.document.getElementById("root"));
Copy the code

It is important to note here that MyInput must be a class component, because function components have no instances and cannot be retrieved by ref. React – redux source, through to be packaging components increase the ref attribute, getWrappedInstance returns the instance this. Refs. WrappedInstance.

if (withRef) {
    this.renderedElement = createElement(WrappedComponent, { ... this.mergedProps,ref: 'wrappedInstance'})}Copy the code

New version (V6 / V7)

React-redux uses the react. ForwardRef method to forward the ref. The forwardRef (withRef) of option is deprecated as of V6. If you want to get an instance of the wrapped component, you need to set the callback of option 4 to true.

/ / MyInput. Js file
import React from 'react';
import { connect } from 'react-redux';

class MyInput extends React.Component {
    render() {
        return (
            <input type="text" />)}}export default connect(null.null.null, { forwardRef: true })(MyInput);
Copy the code

Add ref directly to the wrapped component to obtain the instance of the wrapped component, as shown below:

//index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import MyInput from './MyInput';

function reducer(state, action) {
    return state;
}
const store = createStore(reducer);

function Main() {
    let ref = React.createRef();
    React.useEffect(() = > {
        console.log(ref.current);
    })
    return (
        <Provider store={store}>
            <MyInput ref={ref} />
        </Provider>
    )
}


ReactDOM.render(<Main />.document.getElementById("root"));
Copy the code

Similarly, MyInput must be a class component, because function components have no instances and cannot be retrieved by ref.

React-redux forwards the REF to the Connect component. Pass to the ref of the WrappedComponent via the forwardedRef.

if (forwardRef) {
    const forwarded = React.forwardRef(function forwardConnectRef(props, ref) {
        return <Connect {. props} forwardedRef={ref} />
    })

    forwarded.displayName = displayName
    forwarded.WrappedComponent = WrappedComponent
    return hoistStatics(forwarded, WrappedComponent)
}

/ /...
const{ forwardedRef, ... wrapperProps } = propsconst renderedWrappedComponent = useMemo(
    () = > <WrappedComponent {. actualChildProps} ref={forwardedRef} />,
    [forwardedRef, WrappedComponent, actualChildProps]
)
Copy the code

Reference links:

  • Refs and the DOM
  • Refs forward
  • Hook API index