score:2

Accepted answer

I also ran into this issue trying to initialise my form from redux state, as per the example in https://redux-form.com/7.0.4/examples/initializefromstate/

I ended up getting around it by connecting the component at a higher level, eg:

component.tsx:

interface DummyFormComponentProps {} extends InjectedFormProps

const DummyFormComponent: React.SFC<DummyFormComponentProps> = props => {
  return (
    <form onSubmit={props.handleSubmit}>
      // Fields go here
    </form>
  )
}

export const DummyForm = reduxForm({
  form: "dummy-form"
})(DummyFormComponent)

// Trying to connect here also gave errors with DecoratedComponentClass

container.tsx:

interface DummyFormContainerProps {} extends Pick<InjectedFormProps,
  "initialValues"
>

const submitForm = (formValues: object) => {
  alert(formValues);
};

const DummyFormContainer: React.SFC<DummyFormContainerProps> = props => {  
  return (
    <DummyForm 
      initialValues={props.initialValues}
      onSubmit={submitForm}
    />
  )
}

const mapStateToProps = (state: State) => ({
  initialValues: {}
});
const mapDispatchToProps = (dispatch: object) => {
  return {};
};
export default connect(mapStateToProps, mapDispatchToProps)(DummyFormContainer)

score:-4

I had the same problem and found it was caused by "@types/react-redux", remove this types definition file and everything works the way you would expect it to without any other side effects/type errors caused by not having that type-def-file.

score:0

What we ended up doing was to close our eyes and override the default types with a type declaration file:

redux-forms.d.ts:

declare module 'redux-form' {
  type anyProps = { [key: string]: {} }
  function Field(): React.Component<anyProps>;
  function reduxForm({}): <T>(c: T) => T
  function reducer(): object
  interface SubmissionError {
    new(error?: {}) : Error;
  }
  function getFormValues(formName: string): (formName: {}) => {}
  function stopSubmit(formName: string, errorObject?: {}): any
  function isSubmitting(formName: string): any
  function setSubmitFailed(formName: string): any
  function setSubmitSucceeded(formName: string): any
  function touch(formName: string): any
  function clearSubmitErrors(formName: string): any
  function getFormMeta(formName: string, ...fields: string[]): (state: {}) => {}
  function getFormSyncErrors(formName: string): (state: {}) => {}
  function getFormSubmitErrors(formName: string): (state: {}) => {}
  function getFormNames(): any
}

score:3

I found that I was able to dismiss the error by providing the connect statement with empty TStateProps and TDispatchProps objects.

interface SampleFormData {
  username: string;
}

interface SampleFormProps {
  saveData: (data: SampleFormData) => void;
}

type AllSampleFormProps = SampleFormProps & InjectedFormProps<SampleFormData>;

const SampleForm: React.SFC<AllSampleFormProps> = (props) => (
  <form onSubmit={props.handleSubmit(props.saveData)}>
    <Field name="username" component="input" />
  </form>
);

const DecoratedSampleForm = reduxForm<SampleFormData>({ form: "sampleForm" })(SampleForm);

export default connect<{},{}>(
  () => ({}),
  (dispatch) => ({
    saveData: (data: SampleFormData) => dispatch({ type: "SAVE_DATA", data })
  })
)(DecoratedSampleForm);

The one downside to this is that it forces us to blindly supply connect props but I felt that this was a more elegant solution than writing an override @types declaration.

To address this shortcoming, I was able to validate the types by providing connect with the correct interfaces versus empty objects; however, this method can only be done temporarily to check the bindings as it doesn't resolve the DecoratedComponentClass error.

export default connect<{}, SampleFormProps, InjectedFormProps<SampleFormData>>(
  () => ({}),
  (dispatch) => ({
    saveData: (data: SampleFormData) => dispatch({ type: "SAVE_DATA", data })
  })
)(DecoratedSampleForm);

score:10

Here's a fully typed example that allows initializing a form using initialValues and passing additional props (as IOwnProps):

sampleForm.tsx:

export interface IFormData {
  userId: string;
}

export interface IOwnProps {
  foo: string;
}

export interface IDispatchProps {
  onSubmit: (data: IFormData, dispatch: Dispatch<any>, props: IOwnProps) => void;
}

type AllSampleFormProps = IOwnProps & IDispatchProps & InjectedFormProps<IFormData, IOwnProps>;

const SampleForm: React.SFC<AllSampleFormProps> = (props) => (
  <form onSubmit={props.handleSubmit(props.onSubmit)}>
    <div>foo={props.foo}</div>
    <Field name="userId" component="input" />
    <button type="submit">Submit</button>
  </form>
);

export const DecoratedSampleForm = reduxForm<IFormData, IOwnProps>({})(SampleForm);

sampleForm.ts:

The trick here is to specify proper return type for mapStateToProps, otherwise compiler will be complaining like other authors pointed out.

function mapStateToProps(state: AppState, props: IOwnProps): ConfigProps<IFormData, IOwnProps> {
  return {
    form: "sampleForm", // Form will be handled by Redux Form using this key
    initialValues: {
      userId: state.somethere.userId // Can also be calculated using props
    }
  }
}

function mapDispatchToProps(dispatch: Dispatch<any>): IDispatchProps {
  return {
    onSubmit: (formData: IFormData, dispatch: Dispatch<any>, props: IOwnProps) => {
      console.log(formData);
      console.log(props);
    }
  }
}

export default connect<ConfigProps<IFormData, IOwnProps>>(
  mapStateToProps,
  mapDispatchToProps
)(DecoratedSampleForm);

Now this form can be mounted like this:

<FormContainer foo="bar"/>

Related Query

More Query from same tag