circuit

What is Ref Forwarding?

Passing refs to a child component




Image by Nina Garman from Pixabay

Compared to the rest of the React framework, refs are definitely a bit quirky. React utilizes a declarative design pattern that discourages direct manipulation of the DOM, but for a few use cases—focusing or selecting an input field for example—direct, imperative code is necessary. This is where refs come in, allowing us to access a node and imperatively alter it as needed.

(Aside: If you have no prior experience with React refs I recommend reading this article first before continuing)

A (Broken) Example

To start, let's examine a scenario where ref forwarding might come in handy. Below we have fancy text input along with a button that selects the input's value when clicked. Unfortunately, the example below is broken.

Aside Number Two: despite my best efforts CodeSandBox and/or Medium seem intent on showing you the App file by default. Please be sure to look at the FancyInput.js file as well to see how things vary between implementations

What if we updated our FancyInput component and try to access the ref via props? This time we'll declare a ref attribute on the actual input element and try to pass the ref from the parent component via props.ref.

Broken again? What gives?

Let's take a look at the console output:

Warning: FancyInput: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. ([https://reactjs.org/link/special-props](https://reactjs.org/link/special-props))
    at FancyInput ([https://54gtm.csb.app/src/FancyInput.js:24:41](https://54gtm.csb.app/src/FancyInput.js:24:41))
    at div
    at App ([https://54gtm.csb.app/src/App.js:26:38](https://54gtm.csb.app/src/App.js:26:38))

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `App`.
    at FancyInput ([https://54gtm.csb.app/src/FancyInput.js:24:41](https://54gtm.csb.app/src/FancyInput.js:24:41))
    at div
    at App ([https://54gtm.csb.app/src/App.js:26:38](https://54gtm.csb.app/src/App.js:26:38))

As you can see from the error messages, attempting to pass the ref to the child component via props will not work. Thankfully, we're given the helpful suggestion to use React.forwardRef().

Checking the Documentation

If we follow the link provided in the first warning, we'll see that ref is a special prop/keyword used internally by React, meaning any value passed to an element as a ref is not accessible via props. The same is true of any value passed as a key attribute.

Most props on a JSX element are passed on to the component, however, there are two special props (ref and key) which are used by React, and are thus not forwarded to the component. — Special Props Warning

In other words, React utilizes the ref and key attributes internally, preventing the components to which they belong from accessing those values via props.ref or props.key.

If you need to access the same value within the child component, you should pass it as a different prop (ex: ). While this may seem redundant, it's important to separate app logic from reconciling hints. — Special Props Warning

Does this mean we can pass a ref to a child component by giving it a different name? Well, yes and no. While it is possible, passing a ref as a value to a unique prop is considered bad practice. Why is that?

An Improper Solution

Whenever designing software, it is important to keep the principles of low coupling and high cohesion* in mind. Your program should maintain a proper separation of concerns: *Parent components should not need intimate knowledge of the structure of their children nor should their children require a comprehensive understanding of the parent component to properly function.

While the above code works, it leads to tightly coupled components with blurry cohesion at best, the exact opposite of good design practices. It may be easy to remember to use innerRef when you first create the FancyInput component, but what if that component is reused throughout your application? What if you decide to recycle the component in future projects? What happens when a different developer is doing maintenance a year or more down the road?

When I look at the code above all I can think about is all the future headaches that will be inflicted upon whoever is unlucky enough to maintain, update, or otherwise interact with this codebase. No bueno.

The Preferred Solution: React.forwardRef()

Thankfully, the React devs have provided a solution to our problems. By wrapping our functional component in React.forwardRef() we can pass a second argument, ref, in addition to props that can then be utilized within the child component in whatever way is necessary.

By wrapping FancyInput in React.forwardRef() we can grab the ref passed from the parent component and utilize it internally. This decouples the two files, effectively separating concerns and making our code more modular. Our parent component can now imperatively modify its child without needing detailed knowledge of the child's internal DOM structure.

Class Components

The React documentation states “You can forward refs to class component instances, too.” but sadly doesn't provide any example code. After some perusing of StackOverflow, I was able to find a viable solution.

The trick is to wrap an instance your class in React.forwardRef() during the export statement. React.forwardRef() takes a callback function and passes it two arguments, props and ref. You can write a quick helper function that receives those two arguments and passes the ref to a unique prop (in this case innerRef).

While similar to the previous (bad) example, this strategy differs in that the parent component does not need to know anything about the child's internal structure. With this method, there is no difference between a class or functional component from the parent's perspective, and that's a good thing.

Why should the parent care whether a subcomponent is a class or a function? Is that necessary at that level of abstraction? Perhaps there are a few edge cases where that information is needed, but generally speaking, whether a child is a function or a class should be irrelevant to the logic occurring within a parent. Low coupling, high cohesion.

TL;DR

  • You cannot pass a ref to a child component as you would other values (i.e. via plain old props)

  • To pass a ref to a child component (to forward it), you must wrap the child in React.forwardRef()

  • React.forwardRef() receives a function as an argument with which it provides two arguments, props and a ref

  • Functional components can receive these arguments directly and utilize them as needed

  • Class components require a helper function that maps the ref to a unique prop allowing the class to utilize it internally.

Have you used forwarded refs before in your own projects? Where have you seen them used, and for what purposes? Feel free to share your personal experience in the comments below.




Continue Learning