Typing redux forms v7 with TypeScript and React

后端 未结 5 2058
野性不改
野性不改 2020-12-29 15:29

I\'ve got a plain react-redux-powered form. I wish for there to be a form.container.tsx and a form.component.tsx, where form.container.tsx holds all the connections to redux

相关标签:
5条回答
  • 2020-12-29 15:41

    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.

    0 讨论(0)
  • 2020-12-29 15:44

    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
    }
    
    0 讨论(0)
  • 2020-12-29 15:45

    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);
    
    0 讨论(0)
  • 2020-12-29 16:06

    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)
    
    0 讨论(0)
  • 2020-12-29 16:07

    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"/>
    
    0 讨论(0)
提交回复
热议问题