首页 > 解决方案 > How to cast type on imported react component?

问题描述

I want to be able to do something like this:

import Form as React.Component<IFormProps> from './Form';

So that I can then use the component and it will require the props defined in the IFormProps interface.

I am trying to do this, because my Form component uses redux-form and redux, and the two decorators, simply do not work for me. I've spend too much time googling for examples on how to do this, but nothing works. Here is what my Form export looks like, because nothing else works.

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)((reduxForm as any)(formConfig)(Form)) as any;

It really shouldn't be this hard, I have a IFormProps interface inside my Form component, and all I want is for typescript to recognize the required props inside it.

EDIT: As requested I'm adding more info about what my component looks like, but BE WARNED the component is rather complex as far as typings goes. I've had so much trouble getting these connect and redux-form decorators to work, I've had to do a lot of workarounds. And it's wasted a lot of time. At the end of the day, I just need the component to validate against IFormProps, I don't even care if the decorators can't work together. There doesn't seem to be much help in this regard on here or google for that matter. Anyway, more code below:

Form.tsx

import * as React from 'react';
import {
  clearForm,
  doFormSubmit,
  getFormRelatedValues,
  IClearForm,
  IDoFormSubmit,
  IGetFormRelatedValues,
  ISearchFormRelatedValues,
  IUploadFile,
  searchFormRelatedValues,
  uploadFile,
} from '../actions/formActions';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { reduxForm, SubmissionError } from 'redux-form';

const { Component } = React;

function onSubmitFail(errors: IDCRA.IGenericObj,
  dispatch: IDCRA.IDispatch,
  submitError: IDCRA.IGenericObj,
  props: IFromProps) {
  // Something....
}

function scrollToFirstError() {
  // Something else....
}

const formConfig = {
  onSubmitFail: (
    errors: IDCRA.IGenericObj,
    dispatch: IDCRA.IDispatch,
    submitError: IDCRA.IGenericObj,
    props: IFromProps,
  ) => scrollToFirstError(errors, props),
  returnRejectedSubmitPromise: true,
  validate: someValidate,
};

declare interface IFromProps {
  // setFormWarningMessage?: (msg: string) => void;
  appContext?: string;
  asyncBlurFields?: string[];
  asyncValidate?: IAsyncValidate;
  change?: IDCRA.IChangeFieldValue;
  clearFormConnect?: IClearForm;
  doFormSubmitConnect?: IDoFormSubmit;
  error?: string;
  fields: { [key: string]: IDCRA.IField };
  firstPage: boolean;
  form: string;
  formObj: IDCRA.IForm;
  formPageIdentifier: string;
  getFormRelatedValuesConnect?: IGetFormRelatedValues;
  goToPrevPage?: () => any;
  handleSubmit?: (fn: (values: IDCRA.IGenericObj, dispatch: IDCRA.IDispatch) => any) => any;
  i18n?: IDCRA.IGenericObj;
  initialValues?: IDCRA.IGenericObj;
  invalid?: boolean;
  isCreateMode: boolean;
  lang: string;
  lastPage: boolean;
  onSubmitSuccess: (response: IDCRA.ISaveCardResponseObj) => any;
  ownerIdentifier: string;
  partialSave?: boolean;
  pristine?: boolean;
  rows: string[][];
  searchFormRelatedValuesConnect?: ISearchFormRelatedValues;
  submitButtonLabel?: string;
  submitFailed?: boolean;
  submitting?: boolean;
  untouch?: IDCRA.IUntouchField;
  uploadFileConnect?: IUploadFile;
  waitForEvent?: boolean;
}

class Form extends Component<IFromProps, {}> {
  constructor(props: IFromProps) {
    super(props);

    this.handleFormSubmit = this.handleFormSubmit.bind(this);
  }

  handleFormSubmit(values: IDCRA.IGenericObj, dispatch: IDCRA.IDispatch) {
    // Handles form submit....
  }

  render() {
    // Props are consumed here and used to build the form
    const {handleSubmit, identifier} = this.props;

    return (
      <div className="form" id={`form-container-${identifier}`}>
        <form onSubmit={handleSubmit(this.handleFormSubmit)}>
          <div className="card bg-default">

            {/* my form parts are here, not important */}

          </div>
        </form>
      </div>
    );
  }
}

function mapStateToProps(state: IDCRA.IAppState) {
  return {
    appContext: state.appCoreData.appCoreData.appContext,
    i18n: state.appCoreData.appCoreData.i18n,
  };
}

// I have to use my own dispatch type because by default I get errors...
// It's really hard to debug these deply nested TS errors, the messages are cryptic and could be coming from multple source
function mapDispatchToProps(dispatch: IDCRA.IDispatch) {
  return bindActionCreators(
    {
      // setFormWarningMessage,
      clearFormConnect: clearForm,
      doFormSubmitConnect: doFormSubmit,
      getFormRelatedValuesConnect: getFormRelatedValues,
      searchFormRelatedValuesConnect: searchFormRelatedValues,
      uploadFileConnect: uploadFile,
    },
    dispatch,
  );
}

//  Decorate the form component
export default connect(
  mapStateToProps,
  mapDispatchToProps,
)((reduxForm as any)(formConfig)(Form)) as any;

If I remove my any casts in the statements from the connect, this is the error I get:

TS2345: Argument of type 'typeof Form' is not assignable to parameter of type 'ComponentType<IFromProps & InjectedFormProps<{ [x: string]: any; }, IFromProps>>'.
  Type 'typeof Form' is not assignable to type 'StatelessComponent<IFromProps & InjectedFormProps<{ [x: string]: any; }, IFromProps>>'.
    Type 'typeof Form' provides no match for the signature '(props: IFromProps & InjectedFormProps<{ [x: string]: any; }, IFromProps> & { children?: ReactNode; }, context?: any): ReactElement<any>'.

If I only leave the any on the reduxForm decorator, I get this error.

TS2339: Property 'fields' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<{}, ComponentState, any>> & Readonly<{ children?: ReactNode; }> & Readonly<{}>'.

For what its worth, I don't expect anyone to be able to resolve these errors, there's simply too much going on. And I myself have spent several hours trying to get rid of the errors, and it's just been a game of whack-a-mole.

So, at the end of the day, if I could simply overwrite what TS thinks the component exported is, I'll be happy.

标签: reactjstypescriptreduxredux-form

解决方案


Ok I got it working by doing this:

class Form extends Component<InjectedFormProps & IFromProps, {}> .....

And then export like this:

export default connect<{}, {}, IFromProps, {}>(
  mapStateToProps,
  mapDispatchToProps,
)(reduxForm(formConfig)(Form));

Now my component validates for the correct props!


推荐阅读