Get type definition for props.dispatch() for using TypeScript with React-Redux - reactjs

I have this functional/stateless component:
import React from 'react';
import {useFormik} from 'formik';
import {connect} from "react-redux";
function mapStateToProps(){
return {
foo: "bar"
}
}
interface OwnProps {
propFromParent: number
}
type StateProps = ReturnType<typeof mapStateToProps>
type Props = StateProps & OwnProps
const SignupForm = (props: Props) => {
const formik = useFormik({
initialValues: {
email: '',
name: '',
password: ''
},
onSubmit(values) {
props.dispatch() // props.dispatch is not defined!
}
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="name">Full Name</label>
<input
id="name"
name="name"
type="name"
onChange={formik.handleChange}
value={formik.values.name}
/>
<button type="submit">Submit</button>
</form>
);
};
export default connect<StateProps, null, Props>(mapStateToProps)(SignupForm);
so I am getting this compile error:
So how can I include the type definition so that props.dispatch is defined?
Just looking for help with the proper TS definitions.

Please see the React-Redux docs on "Static Typing", which show how to handle defining the correct types for what connect will pass into your component.
Specifically, we recommend using the ConnectedProps<T> helper, like this:
import { connect, ConnectedProps } from 'react-redux'
interface RootState {
isOn: boolean
}
const mapState = (state: RootState) => ({
isOn: state.isOn
})
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' })
}
const connector = connect(
mapState,
mapDispatch
)
// The inferred type will look like:
// {isOn: boolean, toggleOn: () => void}
type PropsFromRedux = ConnectedProps<typeof connector>
interface Props extends PropsFromRedux {
backgroundColor: string
}
const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)
export default connector(MyComponent)

You need to add a new function and pass it to connect() as second argument
...
function mapDispatchToProps(dispatch): IDispatchProps {
return {
dispatch
};
}
connect(mapStateToProps, mapDispatchToProps)

Related

How do i prevent circular type reference in typescript with redux and connect when using mapState function?

I've been following the official tutorial on using redux with typescript here.
Everything works fine except when I need to specify the incoming props type to Props in mapState.
This is the type error I am having from typescript, which I assume this to mean a circular reference:
'connector' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
Below is the code from the tutorial except with props: Props added to mapState
import { connect, ConnectedProps } from 'react-redux';
interface RootState {
isOn: boolean;
}
const mapState = (
state: RootState,
props: Props // <-- ERROR CAUSED BY ADDING TYPE 'Props' TO 'props'
) => ({
isOn: state.isOn,
});
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
};
const connector = connect(mapState, mapDispatch);
// The inferred type will look like:
// {isOn: boolean, toggleOn: () => void}
type PropsFromRedux = ConnectedProps<typeof connector>;
interface Props extends PropsFromRedux {
backgroundColor: string;
}
const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
);
export default connector(MyComponent);
How do I fix this? Setting type from Props to any will lose all type information. Any help would be much appreciated. Thank you.
Here you have a correct code to handle this case:
import React from 'react'
import { connect, ConnectedProps } from 'react-redux';
interface RootState {
isOn: boolean;
}
interface Props {
backgroundColor: string;
}
const mapState = (
state: RootState,
props: Props
) => ({
...props, // <---- you should add own props here
isOn: state.isOn,
});
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
};
const connector = connect(mapState, mapDispatch);
type PropsFromRedux = ConnectedProps<typeof connector>;
const MyComponent = (props: PropsFromRedux) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
);
const ConnectedComponent = connector(MyComponent)
const render = <ConnectedComponent backgroundColor="white" /> // ok
Since you have added ownProps to mapState, you should use it.
Playground

Property is missing in type '{}' but required in type.... Typescript connect pattern

Hello I need connect pattern for react typescript
I have reducer
type State = {
version: number,
a?: string
}
interface ActionC {
type: string
payload?: number
}
type IAction = ActionA | ActionB | ActionC;
const reducer = (state: State = initialState, action: IAction) => {
// The reducer normally looks at the action type field to decide what happens
switch (action.type) {
// Do something here based on the different types of actions
default:
// If this reducer doesn't recognize the action type, or doesn't
// care about this specific action, return the existing state unchanged
return state
}
}
but when I tying use connect
export const App: FunctionComponent<{ version: number}> = (props) => {
return (
<div>APP {props.version}</div>
)
}
const mapStateToProps = (state: State) => {
return {
version: state.version
}
};
export default connect(mapStateToProps)(App);
I have error
Property 'version' is missing in type '{}' but required in type
'Omit<{ version: number; }, never>'. TS2741
How to fix this problem ?
As i pulled from https://redux.js.org/recipes/usage-with-typescript:
If you are still using connect, you should use the ConnectedProps type exported by #types/react-redux^7.1.2 to infer the types of the props from connect automatically. This requires splitting the connect(mapState, mapDispatch)(MyComponent) call into two parts:
import { connect, ConnectedProps } from 'react-redux'
interface RootState {
isOn: boolean
}
const mapState = (state: RootState) => ({
isOn: state.isOn
})
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' })
}
const connector = connect(mapState, mapDispatch)
// The inferred type will look like:
// {isOn: boolean, toggleOn: () => void}
type PropsFromRedux = ConnectedProps<typeof connector>
type Props = PropsFromRedux & {
backgroundColor: string
}
const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)
export default connector(MyComponent)
Function connect must have 2 arguments, always. Even if you do not use mapDispatchToProps, you must pass it. While in JS null would do, in TS it should be an empty object.
So, your code should be
export default connect(mapStateToProps, {})(App);

Property 'then' does not exist on type 'ThunkAction<Promise<any>, RootState, undefined, any>'. TS2339

I just upgraded react-redux from 4.0.0 to 4.0.5 and my code fails during compile. I get the error
Property 'then' does not exist on type 'ThunkAction, RootState, undefined, any>'. TS2339
import { ThunkAction, ThunkDispatch as Dispatch } from "redux-thunk";
import { RootState } from "../store/reducer";
import { RootAction } from "../store/actions";
export type ThunkResult<R> = ThunkAction<R, RootState, undefined, RootAction>;
export type ThunkDispatch = Dispatch<RootState, undefined, RootAction>;
// Copied from https://github.com/reduxjs/redux-thunk/blob/0e60b6264fb402afb328157c4337f5cc87a07b53/index.d.ts#L63-L67
export type ThunkActionDispatch<
TActionCreator extends (...args: any[]) => ThunkAction<any, any, any, any>
> = (
...args: Parameters<TActionCreator>
) => ReturnType<ReturnType<TActionCreator>>;
Component:
import * as React from "react";
import { connect } from "react-redux";
import { withRouter, RouteComponentProps } from "react-router-dom";
import {
Dialog,
FormGroup,
InputGroup,
Button,
Classes,
} from "#blueprintjs/core";
import { createClient } from "../../store/modules/client";
import { ThunkDispatch } from "../../types/redux";
interface IProps extends RouteComponentProps<any> {
dispatch: ThunkDispatch;
isOpen: boolean;
onClose: () => void;
}
interface IState {
name: string;
isLoading: boolean;
error: string;
}
class CreateClientModal extends React.Component<IProps, IState> {
public state: IState = {
name: "",
isLoading: false,
error: "",
};
public render() {
return (
<Dialog
isOpen={this.props.isOpen}
onClose={this.props.onClose}
title="Add New Client"
>
<div className={Classes.DIALOG_BODY}>
<FormGroup
helperText={this.state.error}
intent={this.state.error ? "danger" : "none"}
label="New Client"
labelFor="client-name-input"
labelInfo="(required)"
>
<InputGroup
id="client-name-input"
placeholder="Client Name"
required={true}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ name: e.target.value })
}
/>
</FormGroup>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button
onClick={this.handleOnClose}
disabled={this.state.isLoading}
>
Cancel
</Button>
<Button
intent="primary"
onClick={this.handleSubmit}
loading={this.state.isLoading}
>
Create Client
</Button>
</div>
</div>
</Dialog>
);
}
private handleOnClose = () => {
this.setState({ error: "" });
this.props.onClose();
};
private handleSubmit = async () => {
this.setState({ isLoading: true });
this.props
.dispatch(createClient(this.state.name))
.then(client => {
this.props.history.push(`/clients/${client.payload.id}`);
})
.catch(error => {
this.setState({
isLoading: false,
error: "Error creating client. Please try again.",
});
// tslint:disable-next-line:no-console
console.error(error);
});
};
}
export default withRouter(connect()(CreateClientModal));
I get the error at this.props.dispatch(createClient(this.state.name)).then(). Please advice on a solution to fix this. Thanks.

TypeScript errors when using Redux-Form with React-Redux connect

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.
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

How to pass rest of props to react component while also having required props defined in an interface

So is this the same issue, or am I just missing something?
import * as React from 'react';
interface Props {
value: string;
}
const MyComponent = (props: Props) => {
const { value, ...rest } = props;
return (
<input {...rest} type="text" value={value} />
);
}
interface ParentState {
searchText: string;
}
class ParentComponent extends React.Component<{}, ParentState> {
state: ParentState = {
searchText: ''
};
onSearchTextChanged = (e: React.FormEvent<HTMLInputElement>) => {
this.setState({
searchText: e.currentTarget.value
});
}
render() {
const { searchText } = this.state;
return (
<div>
<h2>Some Text</h2>
<MyComponent value={searchText} onChange={this.onSearchTextChanged} className="search-input" placeholder="Enter text"/>
// Errors here
</div>
)
}
}
export default ParentComponent
When I have my props for MyComponent defined in an interface, I get the following error:
error TS2339: Property 'onChange' does not exist on type 'IntrinsicAttributes & Props'.
However if I change the props type to any, it compiles just fine.
const MyComponent = (props: any) => {
Is it possible to define the props in an interface so that there are specific props that are required, but also allow additional props to be passed in so I don't have to explicitly add them into the interface?
I'm using TypeScript 2.3.4 and React 15.5.4.
You can avoid excess property/attribute checks by adding a string index signature to your interface:
interface Props {
value: string;
// This is a string index signature.
// This means that all properties in 'Props' are assignable to
// the empty object type ({})
[propName: string]: {};
}
You could also write [propName: string]: any but that generally makes it less safe to use in MyComponent itself.
If you want to forward all properties of the input element inside MyComponent to the props of MyComponent you can lookup what props input (in VSCode for example, use go to definition while on the input tag).
interface IntrinsicElements {
....
input: React.DetailedHTMLProps<React.InputHTMLAttributes<>, HTMLInputElement>;
....
}
We could use React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> as the base type for our props and we will get all properties of input. Since DetailedHTMLProps just adds ref to React.InputHTMLAttributes<HTMLInputElement> we can use just React.InputHTMLAttributes<HTMLInputElement> as the base interface to get all input properties except the ref:
import * as React from 'react'
interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
value: string;
}
const MyComponent = (props: Props) => {
const { value, ...rest } = props;
return (
<input {...rest} type="text" value={value} />
);
}
// Usage
interface ParentState { searchText :string }
class ParentComponent extends React.Component<{}, ParentState> {
state: ParentState = {
searchText: ''
};
onSearchTextChanged = (e: React.FormEvent<HTMLInputElement>) => {
this.setState({
searchText: e.currentTarget.value
});
}
render() {
const { searchText } = this.state;
return (
<div>
<h2>Some Text</h2>
<MyComponent value={searchText} onChange={this.onSearchTextChanged} className="search-input" placeholder="Enter text"/>
</div>
)
}
}
export default ParentComponent

Resources