158

I'm trying to use React.forwardRef, but tripping over how to get it to work in a class based component (not HOC).

The docs examples use elements and functional components, even wrapping classes in functions for higher order components.

So, starting with something like this in their ref.js file:

const TextInput = React.forwardRef(
    (props, ref) => (<input type="text" placeholder="Hello World" ref={ref} />)
);

and instead defining it as something like this:

class TextInput extends React.Component {
  render() {
    let { props, ref } = React.forwardRef((props, ref) => ({ props, ref }));
    return <input type="text" placeholder="Hello World" ref={ref} />;
  }
}

or

class TextInput extends React.Component {
  render() { 
    return (
      React.forwardRef((props, ref) => (<input type="text" placeholder="Hello World" ref={ref} />))
    );
  }
}

only working :/

Also, I know I know, ref's aren't the react way. I'm trying to use a third party canvas library, and would like to add some of their tools in separate components, so I need event listeners, so I need lifecycle methods. It may go a different route later, but I want to try this.

The docs say it's possible!

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

from the note in this section.

But then they go on to use HOCs instead of just classes.

Yangshun Tay
  • 49,270
  • 33
  • 114
  • 141
Han Lazarus
  • 1,883
  • 2
  • 13
  • 11

6 Answers6

177

The idea to always use the same prop for the ref can be achieved by proxying class export with a helper.

class ElemComponent extends Component {
  render() {
    return (
      <div ref={this.props.innerRef}>
        Div has a ref
      </div>
    )
  }
}

export default React.forwardRef((props, ref) => <ElemComponent 
  innerRef={ref} {...props}
/>);

So basically, we are forced to have a different prop to forward ref, but it can be done under the hub. It's important that the public use it as a normal ref.

CrazyVideoGamer
  • 754
  • 8
  • 22
Mr Br
  • 3,831
  • 1
  • 20
  • 24
  • 7
    What do you do if you have your component wrapped in a HOC like React-Redux's `connect` or marterial-ui's `withStyles`? – J. Hesters Nov 10 '18 at 18:00
  • What's the problem with `connect` or `withStyles`? You should wrap all HOCs with `forwardRef` and internally use "safe" prop to pass a ref to the lowest component in the chain. – Mr Br Nov 19 '18 at 10:06
  • Any insight on how to test this in Enzyme when you are using setState? – zero_cool Nov 26 '18 at 23:21
  • Sorry I'll need a bit more details. Not sure what is the problem exactly. – Mr Br Nov 30 '18 at 12:13
  • 2
    I am getting: Invariant Violation: Objects are not valid as a React child (found: object with keys {$$typeof, render}). If you meant to render a collection of children, use an array instead. – Brian Loughnane Apr 30 '19 at 19:38
  • @J.Hesters the solution won't work for `withStyles` because `withStyles` internally uses `innerRef`, just rename your own ref prop to something different like `containerInnerRef` and your are good to go. – Alex Zinkevych Nov 07 '19 at 10:46
  • @MrBr How exactly would you implement exporting with a `React.forwardRef` to a component wrapped with redux's `connect`? I am not sure what you meant by wrapping all HOCs with `forwardRef`. I've been trying to do it but still no luck. Here's how I am doing it: `export default React.forwardRef((props, ref) => connect( mapStateToProps, mapDispatchToProps )() );` – Kayle Apr 14 '20 at 17:37
  • @KAYLE you are using the connect in the wrong way. `connect(...)((props) => )` – Mr Br Apr 15 '20 at 09:37
  • @MrBr where does the React.forwardRef go in this? – Kayle Apr 15 '20 at 10:19
  • @KAYLE as it were in your comment, just update the connect part. – Mr Br Apr 15 '20 at 10:37
  • @MrBr So this is what I did: `export default React.forwardRef( connect( mapStateToProps, mapDispatchToProps )((props, ref) => ) );` and I got this error: Uncaught (in promise) TypeError: Cannot call a class as a function – Kayle Apr 15 '20 at 11:13
  • Yeah, I'm getting `Uncaught TypeError: (0 , _hocName.default) is not a function` – Yourin Aug 03 '20 at 18:16
  • I have this `export default withRouter( connect(mapStateToProps, {getSimilarObj,trackObject})(Comp))` How to pass ref – Vikas Acharya Jan 28 '23 at 04:33
  • Extract default export into a component variable and then wrap it with forwardRef HoC as mentioned above. – Mr Br Jan 29 '23 at 17:34
18
class BeautifulInput extends React.Component {
  const { innerRef, ...props } = this.props;
  render() (
    return (
      <div style={{backgroundColor: "blue"}}>
        <input ref={innerRef} {...props} />
      </div>
    )
  )
}

const BeautifulInputForwardingRef = React.forwardRef((props, ref) => (
  <BeautifulInput {...props} innerRef={ref}/>
));

const App = () => (
  <BeautifulInputForwardingRef ref={ref => ref && ref.focus()} />
)

You need to use a different prop name to forward the ref to a class. innerRef is commonly used in many libraries.

Yangshun Tay
  • 49,270
  • 33
  • 114
  • 141
Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419
  • in your code `beautifulInputElement` is more a function rather than a React Element, it should be like this `const beautifulInputElement = ref && ref.focus()} />` – Olivier Boissé Oct 06 '18 at 13:53
  • `const { innerRef, ...props } = this.props;` gives error `Identifier 'props' has already been declared.` – user31782 Jun 04 '23 at 14:13
13

Basically, this is just a HOC function. If you wanted to use it with class, you can do this by yourself and use regular props.

class TextInput extends React.Component {
    render() {
        <input ref={this.props.forwardRef} />
    }
}

const ref = React.createRef();
<TextInput forwardRef={ref} />

This pattern is used for example in styled-components and it's called innerRef there.

  • 6
    Using a differently-named prop like `innerRef` is completely missing the point. The goal is complete transparency: I should be able to treat a `` as if it was a regular DOM element, but using styled-components approach, I need to remember that this component uses an arbitrarily-named prop for refs instead of just `ref`. – user5670895 Aug 17 '18 at 21:13
  • 3
    In any case, styled-components intends to [drop support for `innerRef`](https://github.com/styled-components/styled-components/issues/1694) in favor of passing the ref to the child using `React.forwardRef`, which is what the OP is trying to accomplish. – user5670895 Aug 17 '18 at 21:19
7

This can be accomplished with a higher-order component, if you like:

import React, { forwardRef } from 'react'

const withForwardedRef = Comp => {
  const handle = (props, ref) =>
    <Comp {...props} forwardedRef={ref} />

  const name = Comp.displayName || Comp.name
  handle.displayName = `withForwardedRef(${name})`

  return forwardRef(handle)
}

export default withForwardedRef

And then in your component file:

class Boop extends React.Component {
  render() {
    const { forwardedRef } = this.props

    return (
      <div ref={forwardedRef} />
    )
  }
}

export default withForwardedRef(Boop)

I did the work upfront with tests & published a package for this, react-with-forwarded-ref: https://www.npmjs.com/package/react-with-forwarded-ref

rpearce
  • 1,720
  • 1
  • 21
  • 29
5

Incase you need to reuse this in many difference components, you can export this ability to something like withForwardingRef

const withForwardingRef = <Props extends {[_: string]: any}>(BaseComponent: React.ReactType<Props>) =>
    React.forwardRef((props, ref) => <BaseComponent {...props} forwardedRef={ref} />);

export default withForwardingRef;

usage:

const Comp = ({forwardedRef}) => (
 <input ref={forwardedRef} />
)
const EnhanceComponent = withForwardingRef<Props>(Comp);  // Now Comp has a prop called forwardedRef
orepor
  • 905
  • 2
  • 13
  • 23
1

In case anyone is still having trouble combining React.forwardRef() with a HOC like react-redux connect() then this is how I got it to work:

export default connect(mapStateToProps, mapDispatchToProps)(
  React.forwardRef((props, ref) => <MyComponent innerRef={ref} {...props} />)
);
vobango
  • 11
  • 3