31
loading...
This website collects cookies to deliver better user experience
mapStateToProps,
mapDispatchToProps
.@types/react-redux
package it's possible to automatically infer types of connected props in the most cases. The way to do that is documented in the React Redux documentation, and in this post we'll see the application on a concrete example. App
component, the implementation (but not the type) details of which are simplified for brevity. The component itself fetches a list of items on mount (via Redux action) and then renders the list, which it receives from the props. Additionally the component is using React router, where it receives the URL params as props from.// types.tsx
export type Item = {
id: number;
text: string;
};
export type AppState = {
loading: boolean;
data: Item[];
};
// actions.ts
export function loadData(): ThunkAction<void, AppState, undefined, PayloadAction<any>> {
// Load data from api
}
export function deleteItem(id: string): ThunkAction<void, AppState, undefined, PayloadAction<any>> {
// Delete an item by id
}
export function addItem(item: Item): ThunkAction<void, AppState, undefined, PayloadAction<any>> {
// Add a new item
}
// App.tsx
import React, { useEffect } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
import { loadData, deleteItem, addItem } from './actions';
import { Item, AppState } from './types';
interface OwnProps extends RouteComponentProps<{ id: string }> {}
interface ConnectedProps {
loading: boolean;
data: Item[];
}
interface DispatchProps {
loadData: typeof loadData;
deleteItem: typeof deleteItem;
addItem: typeof addItem;
}
export type Props = OwnProps & ConnectedProps & DispatchProps;
export const App = ({ loading, data, loadData, ...props }: Props) => {
useEffect(() => {
loadData();
}, [loadData]);
if (loading) {
return <p>Loading...</p>;
}
return (
<div>
<ul>
{data.map((result) => (
<li key={result.id}>{result.text}</li>
))}
</ul>
</div>
);
};
const mapStateToProps: MapStateToProps<ConnectedProps, OwnProps> = (state: AppState, props: OwnProps) => {
return {
loading: state.loading,
data: state.data,
id: props.match.params.id,
};
};
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
loadData,
deleteItem,
addItem,
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
typeof
to infer the types of the actions and the types in mapStateToProps
are basically a combination of AppState
and OwnProps
types. Looks like we're doing a lot of manual type declaration for the types we have already available elsewhere, so why not to use that type information and infer the component props automatically?ThunkAction
type, which in turn returns void
(i.e. nothing). When connecting the component to Redux and running TypeScript in a strict mode, we get the following error:Type 'Matching<ConnectedProps & { loadData: () => void; }, Props>' is not assignable to type 'DispatchProps'.
The types returned by 'loadData(...)' are incompatible between these types.
Type 'void' is not assignable to type 'ThunkAction<void, AppState, undefined, { payload: any; type: string; }>'.
Type 'void' is not assignable to type 'ThunkAction<void, AppState, undefined, { payload: any; type: string; }>'.
is the most important here. Even though the type of the loadData
is () => ThunkAction => void
, due to the way how React-Redux resolves thunks, the actual inferred type will be () => void.
ConnectedProps
helper type becomes useful. It allows inferring connected types from mapStateToProps
and mapDispatchToProps
, plus it will correctly resolve the types for thunks. To start, let's move mapStateToProps
and mapDispatchToProps
to the top of the file and strip them from all the generic type declarations, as they won't be necessary anymore.const mapStateToProps = (state: AppState, props: OwnProps) => {
return {
loading: state.loading,
data: state.data,
id: props.match.params.id,
};
};
const mapDispatchToProps = {
loadData,
deleteItem,
addItem,
};
connector
function by combining the props from Redux. We do it before declaring the component since we'll use this function when creating the Props
type.const connector = connect(mapStateToProps, mapDispatchToProps);
ConnectedProps
helper to extract the types of the connected props. Before that we'll also need to remove our ConnectedProps
and DispatchProps
interfaces.import { ConnectedProps } from 'react-redux';
//...
type PropsFromRedux = ConnectedProps<typeof connector>;
Props
type for the component.interface OwnProps extends RouteComponentProps<{ id: string }> {}
type Props = PropsFromRedux & OwnProps;
export const App = ({ loading, data, loadData, ...props }: Props) => { //.. }
export default connector(App);
import React, { useEffect } from 'react';
import { ConnectedProps, connect } from 'react-redux';
import { RouteComponentProps } from 'react-router-dom';
import { loadData, deleteItem, addItem } from './actions';
import { AppState } from './types';
const mapStateToProps = (state: AppState, props: OwnProps) => {
return {
loading: state.loading,
data: state.data,
id: props.match.params.id,
};
};
const mapDispatchToProps = {
loadData,
deleteItem,
addItem,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
type PropsFromRedux = ConnectedProps<typeof connector>;
interface OwnProps extends RouteComponentProps<{ id: string }> {}
export type Props = PropsFromRedux & OwnProps;
export const App = ({ loading, data, loadData, ...props }: Props) => {
useEffect(() => {
loadData();
}, [loadData]);
if (loading) {
return <p>Loading...</p>;
}
return (
<div>
<ul>
{data.map((result) => (
<li key={result.id}>{result}</li>
))}
</ul>
</div>
);
};
export default connector(App);