Browse Source

Add 'visual components' use case for forwarding refs (#798)

* Add 'visual components' use case for forwarding refs

* Rearrange "forwarding refs" to focus on simple use case

* Minor wording nits to 2018-03-29-react-v-16-3.md

* Minor wording nits to forwarding-refs.md

* Add more info to the forwardRef reference doc

* Minor wording nits to reference-react.md
main
Lucas Duailibe 7 years ago
committed by Dan Abramov
parent
commit
dab9b8b2fd
  1. 11
      content/blog/2018-03-29-react-v-16-3.md
  2. 37
      content/docs/forwarding-refs.md
  3. 12
      content/docs/reference-react.md
  4. 42
      examples/16-3-release-blog-post/fancy-button-example.js
  5. 10
      examples/forwarding-refs/fancy-button-simple-ref.js
  6. 7
      examples/forwarding-refs/fancy-button-simple.js
  7. 38
      examples/reference-react-forward-ref.js

11
content/blog/2018-03-29-react-v-16-3.md

@ -39,18 +39,17 @@ Version 16.3 adds a new option for managing refs that offers the convenience of
## `forwardRef` API ## `forwardRef` API
[Higher-order components](/docs/higher-order-components.html) (or HOCs) are a common way to reuse code between components. Building on the theme context example from above, we might create an HOC that injects the current "theme" as a prop: Generally, React components are declarative, but sometimes imperative access to the component instances and the underlying DOM nodes is necessary. This is common for use cases like managing focus, selection, or animations. React provides [refs](/docs/refs-and-the-dom.html) as a way to solve this problem. However, component encapsulation poses some challenges with refs.
`embed:16-3-release-blog-post/hoc-theme-example.js` For example, if you replace a `<button>` with a custom `<FancyButton>` component, the `ref` attribute on it will start pointing at the wrapper component instead of the DOM node (and would be `null` for functional components). While this is desirable for "application-level" components like `FeedStory` or `Comment` that need to be encapsulated, it can be annoying for "leaf" components such as `FancyButton` or `MyTextInput` that are typically used like their DOM counterparts, and might need to expose their DOM nodes.
We can use the above HOC to wire components up to the theme context without having to use `ThemeContext` directly. For example: Ref forwarding is a new opt-in feature that lets some components take a `ref` they receive, and pass it further down (in other words, "forward" it) to a child. In the example below, `FancyButton` forward its ref to a DOM `button` that it renders:
`embed:16-3-release-blog-post/fancy-button-example.js` `embed:16-3-release-blog-post/fancy-button-example.js`
HOCs typically [pass props through](/docs/higher-order-components.html#convention-pass-unrelated-props-through-to-the-wrapped-component) to the components they wrap. Unfortunately, [refs are not passed through](/docs/higher-order-components.html#refs-arent-passed-through). This means that we can't attach a ref to `FancyButton` if we use `FancyThemedButton`— so there's no way for us to call `focus()`. This way, components using `FancyButton` can get a ref to the underlying `button` DOM node and access it if necessary—just like if they used a DOM `button` directly.
The new `forwardRef` API solves this problem by providing a way for us to intercept a `ref` and forward it as a normal prop: Ref forwarding is not limited to "leaf" components that render DOM nodes. If you write [higher order components](/docs/higher-order-components.html), we recommend using ref forwarding to automatically pass the ref down to the wrapped class component instances.
`embed:16-3-release-blog-post/forward-ref-example.js`
[Learn more about the forwardRef API here.](/docs/forwarding-refs.html) [Learn more about the forwardRef API here.](/docs/forwarding-refs.html)

37
content/docs/forwarding-refs.md

@ -4,9 +4,42 @@ title: Forwarding Refs
permalink: docs/forwarding-refs.html permalink: docs/forwarding-refs.html
--- ---
Ref forwarding is a technique for passing a [ref](/docs/refs-and-the-dom.html) through a component to one of its descendants. This technique can be particularly useful with [higher-order components](/docs/higher-order-components.html) (also known as HOCs). Ref forwarding is a technique for automatically passing a [ref](/docs/refs-and-the-dom.html) 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. The most common scenarios are described below.
Let's start with an example HOC that logs component props to the console: ## Forwarding refs to DOM components
Consider a `FancyButton` component that renders the native `button` DOM element:
`embed:forwarding-refs/fancy-button-simple.js`
React components hide their implementation details, including their rendered output. Other components using `FancyButton` **usually will not need to** [obtain a ref](/docs/refs-and-the-dom.html) to the inner `button` DOM element. This is good because it prevents components from relying on each other's DOM structure too much.
Although such encapsulation is desirable for application-level components like `FeedStory` or `Comment`, it can be inconvenient for highly reusable "leaf" components like `FancyButton` or `MyTextInput`. These components tend to be used throughout the application in a similar manner as a regular DOM `button` and `input`, and accessing their DOM nodes may be unavoidable for managing focus, selection, or animations.
**Ref forwarding is an opt-in feature that lets some components take a `ref` they receive, and pass it further down (in other words, "forward" it) to a child.**
In the example below, `FancyButton` uses `React.forwardRef` to obtain the `ref` passed to it, and then forward it to the DOM `button` that it renders:
`embed:forwarding-refs/fancy-button-simple-ref.js`
This way, components using `FancyButton` can get a ref to the underlying `button` DOM node and access it if necessary—just like if they used a DOM `button` directly.
Here is a step-by-step explanation of what happens in the above example:
1. We create a [React ref](/docs/refs-and-the-dom.html) by calling `React.createRef` and assign it to a `ref` variable.
1. We pass our `ref` down to `<FancyButton ref={ref}>` by specifying it as a JSX attribute.
1. React passes the `ref` to the `(props, ref) => ...` function inside `forwardRef` as a second argument.
1. We forward this `ref` argument down to `<button ref={ref}>` by specifying it as a JSX attribute.
1. When the ref is attached, `ref.current` will point to the `<button>` DOM node.
>Note
>
>The second `ref` argument only exists when you define a component with `React.forwardRef` call. Regular functional or class components don't receive the `ref` argument, and ref is not available in props either.
>
>Ref forwarding is not limited to DOM components. You can forward refs to class component instances, too.
## Forwarding refs in higher-order components
This technique can also be particularly useful with [higher-order components](/docs/higher-order-components.html) (also known as HOCs). Let's start with an example HOC that logs component props to the console:
`embed:forwarding-refs/log-props-before.js` `embed:forwarding-refs/log-props-before.js`
The "logProps" HOC passes all `props` through to the component it wraps, so the rendered output will be the same. For example, we can use this HOC to log all props that get passed to our "fancy button" component: The "logProps" HOC passes all `props` through to the component it wraps, so the rendered output will be the same. For example, we can use this HOC to log all props that get passed to our "fancy button" component:

12
content/docs/reference-react.md

@ -231,7 +231,17 @@ You can also use it with the shorthand `<></>` syntax. For more information, see
### `React.forwardRef` ### `React.forwardRef`
`React.forwardRef` accepts a render function that receives `props` and `ref` parameters and returns a React node. Ref forwarding is a technique for passing a [ref](/docs/refs-and-the-dom.html) through a component to one of its descendants. This technique can be particularly useful with [higher-order components](/docs/higher-order-components.html): `React.forwardRef` creates a React component that forwards the [ref](/docs/refs-and-the-dom.html) attribute it receives to another component below in the tree. This technique is not very common but is particularly useful in two scenarios:
* [Forwarding refs to DOM components](/docs/forwarding-refs.html#forwarding-refs-to-dom-components)
* [Forwarding refs in higher-order-components](/docs/forwarding-refs.html#forwarding-refs-in-higher-order-components)
`React.forwardRef` accepts a rendering function as an argument. React will call this function with `props` and `ref` as two arguments. This function should return a React node.
`embed:reference-react-forward-ref.js` `embed:reference-react-forward-ref.js`
In the above example, React passes a `ref` given to `<FancyButton ref={ref}>` element as a second argument to the rendering function inside the `React.forwardRef` call. This rendering function passes the `ref` to the `<button ref={ref}>` element.
As a result, after React attaches the ref, `ref.current` will point directly to the `<button>` DOM element instance.
For more information, see [forwarding refs](/docs/forwarding-refs.html). For more information, see [forwarding refs](/docs/forwarding-refs.html).

42
examples/16-3-release-blog-post/fancy-button-example.js

@ -1,32 +1,10 @@
class FancyButton extends React.Component { // highlight-range{1-2}
buttonRef = React.createRef(); const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
focus() { {props.children}
this.buttonRef.current.focus(); </button>
} ));
render() { // You can now get a ref directly to the DOM button:
// highlight-next-line const ref = React.createRef();
const {label, theme, ...rest} = this.props; <FancyButton ref={ref}>Click me!</FancyButton>;
// highlight-range{5}
return (
<button
{...rest}
className={`${theme}-button`}
ref={this.buttonRef}>
{label}
</button>
);
}
}
// highlight-next-line
const FancyThemedButton = withTheme(FancyButton);
// We can render FancyThemedButton as if it were a FancyButton
// It will automatically receive the current "theme",
// And the HOC will pass through our other props.
<FancyThemedButton
label="Click me!"
onClick={handleClick}
/>;

10
examples/forwarding-refs/fancy-button-simple-ref.js

@ -0,0 +1,10 @@
// highlight-range{1-2}
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

7
examples/forwarding-refs/fancy-button-simple.js

@ -0,0 +1,7 @@
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}

38
examples/reference-react-forward-ref.js

@ -1,28 +1,10 @@
function enhance(Component) { // highlight-range{1-2}
class Enhanced extends React.Component { const FancyButton = React.forwardRef((props, ref) => (
// ... <button ref={ref} className="FancyButton">
{props.children}
render() { </button>
const {forwardedRef, ...rest} = this.props; ));
// Assign the custom prop "forwardedRef" as a ref // You can now get a ref directly to the DOM button:
// highlight-next-line const ref = React.createRef();
return <Component ref={forwardedRef} {...rest} />; <FancyButton ref={ref}>Click me!</FancyButton>;
}
}
// Intercept the "ref" and pass it as a custom prop, e.g. "forwardedRef"
// highlight-range{1-3}
function enhanceForwardRef(props, ref) {
return <Enhanced {...props} forwardedRef={ref} />;
}
// These next lines are not necessary,
// But they do give the component a better display name in DevTools,
// e.g. "ForwardRef(withTheme(MyComponent))"
const name = Component.displayName || Component.name;
enhanceForwardRef.displayName = `enhance(${name})`;
// highlight-next-line
return React.forwardRef(enhanceForwardRef);
}

Loading…
Cancel
Save