Nathan Bierema
Nathan Bierema

Reputation: 1933

Pass component with arbirtrary/generic props to react-redux connect function in TypeScript

I'm trying to connect a React element with react-redux that has arbitrary/generic props, but it's not compiling correctly. I've tried using JSXElementConstructor and (new (props: Props) => React.Component<Props, any>) instead of ComponentType, but got a different error about how Props could be instantiated with a different type. Here's the code. Any help would be appreciated.

https://github.com/piotrwitek/react-redux-typescript-guide/issues/55 looks related, but that seems to be about the props having a generic, instead of the props themselves being a generic.

Code

import React from "react";
import { connect } from "react-redux";

interface StoreState {
  someState: number;
}

// Tried `React.JSXElementConstructor<Pros>`
// and `(new (props: Props) => React.Component<Props, any>)`
// instead of `React.ComponentType<Props>`, but got a different error.
function createWrapperComponent<Props>(
  componentElement: React.ReactElement<
    Props,
    React.ComponentType<Props> & { update(props: Props): void }
  >
) {
  const props = componentElement.props;
  const UnconnectedComponent = componentElement.type;
  const ConnectedComponent = connect<StoreState, {}, Props, StoreState>(
    (state) => state
  )(UnconnectedComponent);

  return class WrapperComponent extends React.Component<any> {
    static update = UnconnectedComponent.update;
    render() {
      return <ConnectedComponent {...props} />;
    }
  };
}

Playground Link: Provided

Upvotes: 7

Views: 853

Answers (2)

J. Bierema
J. Bierema

Reputation: 61

Update: Here's an approach that avoids the initial cast to any. Now I'm casting the props passed to ConnectedComponent instead. Investigating the type issue there might reveal whether there are more potential issues than the one I mentioned.


What you're doing is not sound. If Props were { someState: string }, your UnconnectedComponent would be expecting its someState prop to be a string, but it would be a number.

If stateProps and dispatchProps should override ownProps, which is what your code is doing now, I'm not sure whether you're going to be able to get the types to work automatically the way you want. TypeScript can't figure out that the following is sound:

function a<Props>(
  b: { [K in keyof Props]: K extends keyof StoreState ? StoreState[K] : Props[K] }
): Omit<Props, keyof StoreState>
{
  return b;
}

Maybe you should bite the bullet and use any. Here's a sample of how you might try to make the caller-facing types safer.

If you instead want ownProps to override stateProps, you would want to provide a mergeProps argument to connect.

However, be warned that when you create a ReactElement via JSX, the type field has type any, which of course won't give you type safety if you then call createComponent.

Upvotes: 2

tcf01
tcf01

Reputation: 1789

Your Props doesn't refer to any types defined. So I guess leave it blank is ok?

function createComponent(componentElement: React.ReactElement<{}, React.ComponentType>) {
  const props = componentElement.props;
  const UnconnectedComponent = componentElement.type;
  const ConnectedComponent = connect<StoreState, {}, {}, StoreState>(state => state)(UnconnectedComponent);

  return <ConnectedComponent {...props} />
}

Upvotes: 1

Related Questions