I was falling in love with TypeScript until I found some very discouraging incompatibilities between Redux-Form with React-Redux.
My goal is wrap a reduxForm
decorated component with the react-redux connect
decorator—this pattern has always worked for me in babel configurations and seems to follow the HOC methodology. Here's an example:
import * as React from 'react';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { reduxForm, Field, InjectedFormProps } from 'redux-form';
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);
Here's the errors TypeScript is throwing:
> Argument of type 'DecoratedComponentClass<SampleFormData,
> Partial<ConfigProps<SampleFormData, {}>>>' is not assignable to
> parameter of type 'ComponentType<{ saveData: (data: SampleFormData) =>
> { type: string; data: SampleFormData; }; }>'.
>
> Type 'DecoratedComponentClass<SampleFormData,
> Partial<ConfigProps<SampleFormData, {}>>>' is not assignable to type
> 'StatelessComponent<{ saveData: (data: SampleFormData) => { type:
> string; data: SampleFormData; };...'.
>
> Type 'DecoratedComponentClass<SampleFormData,
> Partial<ConfigProps<SampleFormData, {}>>>' provides no match for the
> signature '(props: { saveData: (data: SampleFormData) => { type:
> string; data: SampleFormData; }; } & { children?: ReactNode; },
> context?: any): ReactElement<any>'.
Has anyone found a solution to make react-redux accept the DecoratedComponentClass
type? I found a suggestion to use a "middle" component but I haven't managed to get this to work with thunk actions. Plus I've found that this creates more problems than it solves in terms of typing the form's props.
TypeScript is a typed superset of JavaScript that provides compile-time checking of source code. When used with Redux, TypeScript can help provide: Type safety for reducers, state and action creators, and UI components.
React Redux recently released version 7.1, which includes long awaited support for React Hooks. This means that you can now ditch the connect higher-order component and use Redux with Hooks in your function components.
We specifically recommend that most form state probably shouldn't be kept in Redux. However, it's your app, so you should evaluate the specific use cases you need to deal with to determine the right approach.
To anyone who comes across this, 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);
A higher Component Interface declaration does the trick that wraps up connects Type with component state and props Type using a decorator.
connect.ts
import * as React from "react";
import {
connect as originalConnect,
MapStateToPropsParam,
MergeProps,
Options
} from "react-redux";
import { IState } from "./index";
export interface IDisPatchProps {
[key: string]: () => void;
}
export type InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> = <
TComponent extends React.ComponentType<TInjectedProps & TNeedsProps>
>(
component: TComponent
) => TComponent;
export interface IConnectProps {
<TStateProps = {}, TDispatchProps = {}, TOwnProps = {}>(
mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps, IState>,
mapDispatchToProps?: IDisPatchProps
): InferableComponentEnhancerWithProps<
TStateProps & TDispatchProps,
TOwnProps
>;
<TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, TMergedProps = {}>(
mapStateToProps?: MapStateToPropsParam<TStateProps, TOwnProps, IState>,
mapDispatchToProps?: IDisPatchProps,
mergeProps?: MergeProps<
TStateProps,
TDispatchProps,
TOwnProps,
TMergedProps
>,
options?: Options<TStateProps, TOwnProps, TMergedProps>
): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>;
}
declare module "react-redux" {
// tslint:disable-next-line
interface Connect extends IConnectProps {}
}
export const connect = originalConnect as IConnectProps;
***ClassFile***
@connect(
(state: IState): IStateProps => ({
count: state.counter.count,
isLoading: state.counter.isLoading
}),
{
decrement,
increment
}
)
export default class MyApp
Link: https://github.com/TomasHubelbauer/react-redux-typescript-connect-decorator-demo/blob/master/my-app/src/connect.ts
credit goes to: TomasHubelbauer https://github.com/TomasHubelbauer
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With