Forward ref through React Router's withRouter HOC

Viewed 5033

I'd like to manage to focus on a component that I've wrapped with withRouter. However, when I give the component a ref, I get the a warning about assigning a ref to a stateless component. I'm assuming this is because the ref is being attached to the withRouter HOC and not my component, as it is stateful. My general set up looks like this:

// InnerComponent.js

class InnerComponent extends Component {
   constructor(props) {
      super(props);
   }
}

export default withRouter(InnerComponent);

// App.js

class App extends Component {
   constructor(props) {
      super(props);
      this.myRef = React.createRef();
   }

render() {
    return (
       <Router>
           <InnerComponent ref={this.myRef}>
       </Router>
    );
}

I see this question has been asked before, but never answered. I'm new to React so please forgive me if I'm missing something obvious. Thanks in advance.

EDIT: I'm fairly sure what I need is here: https://reacttraining.com/react-router/web/api/withRouter, in the wrappedComponentRef section of the withRouter docs, but I don't understand how to implement it.

7 Answers

Based on @Ranjith Kumar answer I came up with the following solution that:

  • Is a bit shorter/simpler (no need for class component or withRef option)
  • Plays a bit better in tests and dev tools
const withRouterAndRef = Wrapped => {
  const WithRouter = withRouter(({ forwardRef, ...otherProps }) => (
    <Wrapped ref={forwardRef} {...otherProps} />
  ))
  const WithRouterAndRef = React.forwardRef((props, ref) => (
    <WithRouter {...props} forwardRef={ref} />
  ))
  const name = Wrapped.displayName || Wrapped.name
  WithRouterAndRef.displayName = `withRouterAndRef(${name})`
  return WithRouterAndRef
}

Usage is the same:

// Before
export default withRouter(MyComponent)
// After
export default withRouterAndRef(MyComponent)

HOC component to forward inner component refs with withRouter HOC

const withRouterInnerRef = (WrappedComponent) => {

    class InnerComponentWithRef extends React.Component {    
        render() {
            const { forwardRef, ...rest } = this.props;
            return <WrappedComponent {...rest} ref={forwardRef} />;
        }
    }

    const ComponentWithRef = withRouter(InnerComponentWithRef, { withRef: true });

    return React.forwardRef((props, ref) => {
        return <ComponentWithRef {...props} forwardRef={ref} />;
      });
}

Usage

class InnerComponent extends Component {
   constructor(props) {
      super(props);
   }
}

export default withRouterInnerRef(InnerComponent);

Finally, I have done this way! this will work for sure

import { withRouter } from 'react-router';

//Just copy and add this withRouterAndRef HOC

const withRouterAndRef = (WrappedComponent) => {
class InnerComponentWithRef extends React.Component {    
      render() {
          const { forwardRef, ...rest } = this.props;
          return <WrappedComponent {...rest} ref={forwardRef} />;
      }
  }
  const ComponentWithRouter = withRouter(InnerComponentWithRef, { withRef: true });
  return React.forwardRef((props, ref) => {
      return <ComponentWithRouter {...props} forwardRef={ref} />;
    });
}

class MyComponent extends Component {
   constructor(props) {
      super(props);
   }
}

//export using withRouterAndRef

export default withRouterAndRef (MyComponent)

I think you could use React.forwardRef (https://reactjs.org/docs/forwarding-refs.html) like this:

// InnerComponent.js

class InnerComponent extends Component {
   constructor(props) {
      super(props);
   }
}

export default withRouter(InnerComponent);

// App.js

const InnerComponentWithRef = React.forwardRef((props, ref) => <InnerComponent ref={ref} props={props} />);

class App extends Component {
   constructor(props) {
      super(props);
      this.myRef = React.createRef();
   }

render() {
    return (
       <Router>
           <InnerComponentWithRef ref={this.myRef}>
       </Router>
    );
}

Note: Untested code!

I thing you can use withRef option available while exporting the component.

See the below sample to export

    // InnerComponent.js

    class InnerComponent extends Component {
       constructor(props) {
          super(props);
          this.exampleMethod = this.exampleMethod.bind(this);
       }

       exampleMethod(){

       }
    }

export default withRouter(InnerComponent , { withRef: true });

and also try the below method to access the methods using reference

this.myRef.getWrappedInstance().exampleMethod()

A much easier way to do this:

    // InnerComponent.js
    class InnerComponent extends Component {
       constructor(props) {
          super(props);
       }
    }

    export default withRouter(InnerComponent);

    // App.js

    import InnerComponentWithRouter, { InnerComponent } from '/InnerComponent'

    class App extends Component {
        private myRef: InnerComponent|null = null;
        constructor(props) {
            super(props);
        }

        render() {
            return (
                <Router>
                    <InnerComponentWithRouter wrappedComponentRef={(r: InnerComponent) => this.myRef = r} />
                </Router>
            );
        }
    }

Simplified the good answer by @pandaiolo a bit more. Uses wrappedComponentRef, which is already used by withRouter

function withRouterAndRef(WrappedComponent) {
    const RoutableComponent = withRouter(WrappedComponent);
    const RoutableComponentWithRef = React.forwardRef((props, ref) => (
        <RoutableComponent {...props} wrappedComponentRef={ref} />
    ));
    const name = WrappedComponent.displayName || WrappedComponent.name;
    RoutableComponentWithRef.displayName = `withRouterAndRef(${name})`;
    return RoutableComponentWithRef;
}
Related