0

Apologies for the rubbish question name. Here are the details.

I have a HoC/Mixin function

export function MyMixin<TOriginalProps>(WrappedComponent: React.ComponentClass<TOriginalProps & IExtraProps>): React.ComponentClass<TOriginalProps> {
  return class extends React.Component<TOriginalProps> {
    public render() {
      const value = "something irrelevent to the question"
      const props = {...this.props, extraProp: value}
      return <WrappedComponent {...props} />;
    }
  }
}

export interface IExtraProps {
  extraProp: string;
}

Now this works great for Components that have their own props such as

class MyComponent extends React.Component<MyProps & IExtraProps, MyState>

MyMixin(MyComponent) // this returns React.ComponentClass<TMyProps,any>

however for Components that don't have any props (apart from the props to 'mix in') it gives me something a little weird

class MyComponentNoProps extends React.Component<IExtraProps, MyState>

MyMixin(MyComponentNoProps) // this return React.ComponentClass<{children?: React.Node; extraProp: string;}, any>

I've tried adapting the MixinFunction to take default values for the type parameter, but I just can't get it to take.

Any ideas how I get a single function (overloads would be fine too) to handle this?

I've tried an overload that has no TOriginalProps Type, but it doesn't resolve properly. If I define that first, then everyone uses it and my components that should have props don't. If I define it second, no one uses it.

I'm guessing this is related to the fact I'm using React as it something to be inferring this weird {children: React.Node; extraProps: string} type from somewhere (

lawrence-witt
  • 8,094
  • 3
  • 13
  • 32
Dave
  • 2,829
  • 3
  • 17
  • 44

1 Answers1

1

An overloaded version which targets IExtraProps specifically seems to produce the behaviour you are looking for:

interface MyProps {
  originalProp: number;
}

interface IExtraProps {
  extraProp: string;
}

interface MyState {
  value: string;
}

export function MyMixin(
  WrappedComponent: React.ComponentClass<IExtraProps>
): React.ComponentClass;

export function MyMixin<TOriginalProps>(
  WrappedComponent: React.ComponentClass<TOriginalProps & IExtraProps>
): React.ComponentClass<TOriginalProps>;

export function MyMixin(
  WrappedComponent: React.ComponentClass<any>
): React.ComponentClass {
  return class extends React.Component {
    public render() {
      const value = "something irrelevent to the question"
      const props = {...this.props, extraProp: value}
      return <WrappedComponent {...props} />;
    }
  }
}

class MyComponent extends React.Component<MyProps & IExtraProps, MyState>{};
class MyComponentNoProps extends React.Component<IExtraProps, MyState>{};

const Test1 = MyMixin(MyComponent);
const Test2 = MyMixin(MyComponentNoProps);

function App() {
  return (
    <>
      <Test1 originalProp={42} /> // OK 
      <Test2 /> // OK
    </>
  )
}
lawrence-witt
  • 8,094
  • 3
  • 13
  • 32
  • Thank you for the suggestion, however when I try this I don't get the same results. Essentially both Component and ComponentNoProps call into which ever overload is defined first, since both can be met. Both implement IExtraProps so the non generic 'fixed' IExtraProps method can apply and since TOriginalProps can just be {} the second overload seems to match both too – Dave Oct 04 '21 at 13:05
  • @Dave I can't reproduce your issue - [playground](https://tsplay.dev/mZa79N). Could you try and create a minimal test case where this fails? – lawrence-witt Oct 04 '21 at 14:05