preface

This article is free translation, translation process mixed with my own understanding, if misleading, please give up reading.

Forwarding Refs

Ref Forwarding is a technique for automatically passing the Ref hook to a component’s descendants. For most components of an application, this technique is not necessary. However, it is particularly useful for individual components, especially those libraries of reusable components. The following documents describe the most common application scenarios of this technique.

The body of the

Pass refs to DOM Components

Suppose we have a component called FancyButton that renders a native DOM element on the interface -button:

function FancyButton(props) {
  return (
    <button className="FancyButton">
      {props.children}
    </button>
  );
}
Copy the code

React components typically hide their implementation details, including their UI output. It is unlikely that any other component that references

would want to get a ref and then access the native DOM element inside

. An ideal usage scenario is to try not to rely on each other’s DOM structure when components reference each other.

For some application-level components, such as the

and

components (the original documentation does not provide the implementation code for these two components, so we can only assume their names), this encapsulation is desirable. However, this encapsulation is very inconvenient to achieve high reusability for some “leaf” (level) components such as

and

. For most of the project, we tend to use these “leaf” components as if they were real DOM nodes, buttons and inputs. These scenes might be managing focus of elements, text selection, or animation-related operations. For these scenarios, access to the actual DOM elements of the component is unavoidable.



Ref Forwarding is an optional feature of the component. Once a component has this feature, it can take a ref from a component and pass it on to its children.

In the following example, the

can take a ref from the react. forwardRef and pass it to its child, a native DOM element called the button:

const FancyButton = React.forwardRef((props, ref) = > (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// You can't mount ref properties directly on function Component if you don't have react. createRef enabled.
// Now you can do this and access native DOM elements:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Copy the code

In this way, components that use

can access the corresponding underlying native DOM element by mounting the ref onto the

component – as if accessing the DOM element directly.

Here’s a step-by-step explanation of how it all happened:

  1. We generate a React ref by calling React. CreateRef and assign it to the ref variable.
  2. We manually assign to<FancyButton>The React REF property further passes the React ref.
  3. React then passes ref to the function passed in when the React. ForwardRef () call is called(props, ref) => .... Ref will then be the second argument to the function.
  4. in(props, ref) => ...Inside the component, we pass the ref as part of the UI output<button ref={ref}>Components.
  5. when<button ref={ref}>When the component is actually mounted to the page, we can use itref.currentTo access the real DOM element button.

Note that the second argument ref mentioned above only exists if you define the component by calling the react.forwardref (). Normal function Component and class Component do not receive this ref parameter. Also, ref is not an attribute of props.

The Ref Forwarding technique is not just used to pass refs to the DOM Component. It is also useful for passing a ref to the Class Component so that you can get an instance reference to the class Component.

Considerations for component library maintainers

When you introduce forwardRef into your component library, you should consider it a breaking change and make a major version of your library. This is because once you introduce this feature, your library will behave differently (e.g. What refs get assigned to, and what types are exported), which will break the normal functionality of other libraries and entire applications that rely on older ref functions.

Use the React. ForwardRef conditionally, and even if you do, we recommend not using it. The React. ForwardRef changes the behavior of your library and will break the function of your app when you update the React version.

Forwarding refs in higher-order components

This technique is also particularly useful for higher-order components. Let’s say we want to implement a higher-order component of printing props.

function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:'.this.props);
    }

    render() {
      return <WrappedComponent {. this.props} / >;
    }
  }

  return LogProps;
}
Copy the code

The higher-order component logProps passes all props to the WrappedComponent, so the higher-order component’s UI output will be the same as the WrappedComponent’s UI output. For example, we’re going to use this higher-order component to pass our props to

.

class FancyButton extends React.Component {
  focus() {
    // ...
  }

  // ...
}

// Rather than exporting FancyButton, we export LogProps.
// It will render a FancyButton though.
export default logProps(FancyButton);
Copy the code

One thing to note about the above example is that the refs are not actually passed down (to the WrappedComponent). This is because ref is not really a prop. Like keys, they are not really prop, but are used for the internal implementation of React. Passing a ref directly to a higher-order component as in the example above will refer to the ContainerComponent instance instead of the Wrapper Component instance:

import FancyButton from './FancyButton';

const ref = React.createRef();

// The FancyButton component we imported is the LogProps HOC.
// Even though the rendered output will be the same,
// Our ref will point to LogProps instead of the inner FancyButton component!
// This means we can't call e.g. ref.current.focus()
<FancyButton
  label="Click Me"
  handleClick={handleClick}
  ref={ref}
/>;
Copy the code

Luckily, we can explicitly pass refs inside the FancyButton component by calling the react. forwardRef API. React. ForwardRef receives a render function that gets two arguments: props and ref. Examples are as follows:

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:'.this.props);
    }

    render() {
    + const{forwardedRef, ... rest} =this.props;

      // Assign the custom prop "forwardedRef" as a ref
    + return<Component ref={forwardedRef} {... rest} />; - return <Component {... this.props} />; } } // Note the second param "ref" provided by React.forwardRef. // We can pass it along to LogProps as a regular prop, e.g. "forwardedRef" // And it can then be attached to the Component. + return React.forwardRef((props, ref) => { + return <LogProps {... props} forwardedRef={ref} />; +}); }Copy the code

Display a custom name in DevTools

React. ForwardRef receives a Render function. React DevTools will use this function to determine what the Ref Forwarding component name should look like.

For example, the following WrappedComponent is the ref Forwarding Component. React DevTools will now say “ForwardRef” :

const WrappedComponent = React.forwardRef((props, ref) = > {
  return <LogProps {. props} forwardedRef={ref} />;
});
Copy the code

If you’ve named the render function React DevTools will include the name in the ref Forwarding component (see “ForwardRef(myFunction)”) :

const WrappedComponent = React.forwardRef(
  function myFunction(props, ref) {
    return <LogProps {. props} forwardedRef={ref} />; });Copy the code

You can even include the wrappedComponent name as part of the Render Function’s displayName (as follows, ForwardRef(logProps(${wrappedComponent.name}))

function logProps(Component) {
  class LogProps extends React.Component {
    // ...
  }

  function forwardRef(props, ref) {
    return <LogProps {. props} forwardedRef={ref} />; } // Give this component a more helpful display name in DevTools. // e.g. "ForwardRef(logProps(MyComponent))" const name  = Component.displayName || Component.name; forwardRef.displayName = `logProps(${name})`; return React.forwardRef(forwardRef); }Copy the code

React.forwardRef -> logProps -> wrappedComponent If the wrappeedComponent is our react. forwardRef wrapped FancyButton, the path can be longer: ForwardRef -> function -> function -> function -> function -> function -> function