Search code examples
reactjstypescriptreduxref

Getting redux's connect HOC's wrapped component type to use with React.RefObject without re-import


I have a simple component that's wrapped with react-redux and I have a ref to that component with forwardRef: true, like so:

// Button.tsx

class Button extends React.Component {
  // ...
}

// ...

export default connect(mapStateToProps, null, null, { forwardRef: true })(Button);

Now I have a parent component which renders the Button component

// ButtonParent.tsx

import Button from './Button';

class ButtonParent extends React.Component {
  buttonRef: React.RefObject<Button>;
  public constructor(props) {
    this.buttonRef = React.createRef()
  }

  render() {
    return (
      <>
        <Button ref={this.buttonRef}/>
      </>
    );
  }
}

Please note that this is a minimal simplified repro of the issue without unnecessary typings.

The issue is with the type of buttonRef. It classifies Button (which is the default export from Button.tsx) as a value, which is correct. It also suggests me to use typeof, which is incorrect.

The issue stems from the fact that Button is the HOC from redux's connect, but the ref's type is the actual Button class component.

It is possible to solve this by renaming and re-exporting the Button component, this way:

// Button.tsx

class _Button extends React.Component {
  // ...
}

// ...

export default connect(mapStateToProps, null, null, { forwardRef: true })(_Button);

and then using buttonRef: React.RefObject<_Button>.

But I wonder if there's an easier / cleaner way to do it, maybe redux somehow exports the wrapped component's type and I just don't know how?


Solution

  • Edit: a simpler extracting type:

    type WrappedComponentType<C extends { WrappedComponent: any }> = InstanceType<C['WrappedComponent']>;
    

    I asked the question almost a year ago and today I encountered a similar problem and eventually found this thread again. In the last year I've got better at TypeScript so I've decide to re-tackle this and try to solve this, fortunately I found a solution!

    You can infer the type by using redux's ConnectedComponent type with this helper:

    import React from "react";
    import { ConnectedComponent } from "react-redux";
    
    type WrappedComponentType<C> = C extends ConnectedComponent<any, infer T>
      ? T extends { ref?: React.LegacyRef<infer WrappedComponent> | undefined }
        ? WrappedComponent
        : never
      : never;
    

    and then the type of buttonRef:

    this.buttonRef = React.createRef<WrappedComponentType<typeof Button>>();
    

    If you're using functional components:

    let buttonRef = useRef<WrappedComponentType<typeof Button>>(null);