reactjsreact-routerreact-forwardrefreact-hoc

what is the right way to use forwardRef with withRouter


I just tried to use forwardRef with a withRouter(mycomponent) like this :

export default function App() {

  const childRef = useRef();
  const childWithRouteRef = useRef();

  useEffect(()=>{
    console.log("childWithRouteRef",childWithRouteRef);
    childRef.current.say();
    childWithRouteRef.current.say();
  })


  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <BrowserRouter>
      <Child ref={childRef}/>
      <ChildWithRoute_ ref={childWithRouteRef}/>
      </BrowserRouter>
    </div>
  );
}

const Child = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
        say: () => {
      console.log("hello")
        },
  }));

  return <div>Child</div>
})

const ChildWithRoute = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
        say: () => {
      console.log("hello")
        },
  }));

  return <div>ChildWithRoute</div>
})

const ChildWithRoute_ = withRouter(ChildWithRoute)

if I wrapped my component in a withRouter HOC, the ref will not working, it's always null. so how can I use forwardRef with a component wrapped in withRouter?


Solution

  • Forwarding refs in higher order components

    ... refs will not get passed through. That’s because ref is not a prop. Like key, it’s handled differently by React. If you add a ref to a HOC, the ref will refer to the outermost container component, not the wrapped component.

    Looks as though the withRouter HOC doesn't yet forward refs. You can create your own little HOC to also forward a ref to the decorated-with-router component

    const withRouterForwardRef = Component => {
      const WithRouter = withRouter(({ forwardedRef, ...props }) => (
        <Component ref={forwardedRef} {...props} />
      ));
    
      return forwardRef((props, ref) => (
        <WithRouter {...props} forwardedRef={ref} />
      ));
    };
    

    Usage:

    const ChildWithRoute = forwardRef((props, ref) => {
      useImperativeHandle(ref, () => ({
        say: () => console.log("hello from child with route"),
      }));
    
      return <div>ChildWithRoute</div>;
    })
    
    const ChildWithRouteAndRef = withRouterForwardRef(ChildWithRoute);
    
    ...
    <ChildWithRouteAndRef ref={childWithRouteRef} />
    

    Edit forwardRef - HOC

    After a quick google search I found this issue, and based on the timestamps and last comment seems unlikely to be resolved. My above solution is similar to several approaches shared.