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
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.
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
}
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);
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)
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"/>