Typing mapStateToProps React Redux - reactjs

I am not able to type the "state" parameter of a mapStateToProps
If I just change state : any instead of state: AppState it works and no error.
But I would like to have a correct typing for my state parameter.
For now, I have this error on the mapStateToProps param of the connect()
No overload matches this call.
The last overload gave the following error.
Argument of type '(state: { quiz: IQuizInitialState; }) => StateProps' is no assignable to parameter of type 'MapStateToPropsParam'.
Cannot assign the type '(state: { quiz: IQuizInitialState; }) => StateProps' to type 'MapStateToPropsFactory'.
Parameters 'state' and 'initialState' are not compatible.
Property 'quiz' is missing in type '{}' but required in type '{ quiz: IQuizInitialState; }'.ts(2769)
interface OwnProps {
}
interface StateProps {
}
interface DispatchProps {
}
type Props = OwnProps & StateProps & DispatchProps;
export class App extends Component<Props> {
render() {
return (
<div>Hey</div>
);
}
}
const mapStateToProps = (state: AppState): StateProps => ({
})
const mapDispatchToProps = (dispatch: ThunkDispatch<{}, {}, AnyAction>): DispatchProps => {
return {
}
}
// The args 'mapStateToProps' generate the error
export default connect<StateProps,DispatchProps,OwnProps>(mapStateToProps, mapDispatchToProps)(App)
This is my rootReducer :
import { combineReducers } from 'redux';
import { QuizReducer } from './quiz';
const rootReducer = combineReducers({
quiz: QuizReducer
});
export type AppState = ReturnType<typeof rootReducer>
export default rootReducer;
And the single reducer is :
import { TYPES } from '../actions/action-types';
import { IQuizListItem, Action } from '../models/index';
import { AnyAction } from 'redux';
export interface IQuizInitialState {
quizListItem: IQuizListItem[]
}
const quizInitialState: IQuizInitialState = {
quizListItem: []
}
export const QuizReducer = (state = quizInitialState, action: AnyAction): IQuizInitialState => {
switch (action.type) {
case TYPES.getQuizListItems:
return {
...state,
quizListItem: (action as Action<IQuizListItem[]>).payload
}
default:
return state
}
}
Thank you by advance guys !

The type of your state is the same type you use for the whole state. since mapStateToProps takes the whole state to pass it down to selectors. in your case I believe this would be the correct type IQuizInitialState.
const mapStateToProps = (state: IQuizInitialState): StateProps => ({
})
EDIT
In your comment you mention IQuizInitialState isnt your whole application state. Then that one is not the one you need. You need a type for the whole application state. To achieve that you could create an interface for every single reducer type meaning your IQuizInitialState but for the other reducers into a single interface.
Ill have to asume here since I dont have your code base but consider
combineReducers({potato: quizReducer, tomato: otherReduzer})
you'll need a type
interface IApplicationState {
potato: IQuizInitialState;
tomato: IOTherInterfaceYouDefinedForThisReducer;
}
your combineReducers will probable look like :
combineReducers<IApplicationState>({
potato: quizReducer,
tomato: otherReduzer
});
I hope you get the idea.
EDIT 2
After reading your last comment I noticed you are asking for the mapStateToProps with two arguments. and you are just defining one. Your connect generics seems wrong then. you should consider the following:
connect<StateProps, DispatchProps, Props, IApplicationState>
where:
StateProps : describes what was returned by mapStateToProps()
DispatchProps: describes what is returned by dispatchToProps()
Props: Your component props
IApplicationState: Represents your Apps Redux whole state

Related

How to use props from connect without having to explicitly write them when rendering component?

I am using redux/connect to get state and actions creators as props in my component. This, however, causes an error with Typescript when rendering the component as the props i'm receiving via connect are not explicitly written.
Is there a way to get around this without making the types optional or is there something else I am missing?
//FetchData Component
import React, {Component} from 'react';
import { connect } from "react-redux";
import {
getData,
filterMaterial,
filterSize,
resetMap
} from "../js/actions/index";
import { MapProperties } from '../types/state';
import { Appstate } from '../js/store';
type DispatchProps = {
getData: () => void,
filterMaterial: (name:string) => void,
filterSize: (name:string) => void,
resetMap: () => void
}
type Props = DispatchProps & LinkStateProps
class FetchData extends Component<Props> {
constructor(props: Props) {
super(props);
this.handleReset = this.handleReset.bind(this)
}
handleReset = () => {
if(this.props.resetMap) this.props.resetMap();
}
componentDidMount() {
if (this.props.getData) this.props.getData()
}
render() {
return (
...
);
}
}
type LinkStateProps = {
geoJSON: MapProperties,
mapJSON: MapProperties
}
const mapStateToProps = (state: Appstate, props:Props):LinkStateProps => {
return {
geoJSON: state.geoJSON,
mapJSON: state.mapJSON
};
};
export default connect(mapStateToProps, {
getData,
filterMaterial,
filterSize,
resetMap
})(FetchData);
//App Component
import React from 'react';
import './App.scss';
import FetchData from './components/FetchData';
function App() {
return (
<div className="App">
//error = Type '{}' is missing the following properties from type 'DispatchProps': getData, filterMaterial, filterSize, resetMap
<FetchData />
</div>
);
}
export default App;
It's because you're passing Props into your mapToState function.
try:
- const mapStateToProps = (state: Appstate, props:Props):LinkStateProps => {
+ const mapStateToProps = (state: Appstate):LinkStateProps => {
return {
geoJSON: state.geoJSON,
mapJSON: state.mapJSON
};
};
The second argument to mapToState is an optional ownProps argument that is only needed if you need any of the component's "own" props (not connected props) to create the proper mapping from state to props. Providing Props as you did makes TypeScript assume that those props must be provided explicitly when using the component, hence the error you were seeing.
Also, you might consider using some built-in features that TypeScript offers to save you from typing things that TypeScript can infer types from automatically.
Consider this:
import React, {Component} from 'react';
import { connect } from "react-redux";
import {
getData,
filterMaterial,
filterSize,
resetMap
} from "../js/actions/index";
import { MapProperties } from '../types/state';
import { Appstate } from '../js/store';
const mapStateToProps = (state: Appstate) => ({
geoJSON: state.geoJSON,
mapJSON: state.mapJSON
});
const dispatchProps = {
getData,
filterMaterial,
filterSize,
resetMap
}
// You could do:
// type LinkStateProps = ReturnType<typeof mapStateToProps>;
// type DispatchProps = typeof dispatchProps;
// type Props = LinkStateProps & DispatchProps;
// or in one line:
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
class FetchData extends Component<Props> {
constructor(props: Props) {
super(props);
this.handleReset = this.handleReset.bind(this)
}
handleReset = () => {
if(this.props.resetMap) this.props.resetMap();
}
componentDidMount() {
if (this.props.getData) this.props.getData()
}
render() {
return (
<p>something</p>
);
}
}
export default connect(mapStateToProps, dispatchProps)(FetchData);
Creating an object named dispatchProps (which you do anyway to supply to connect) lets you use Typescript's typeof operator to auto-generate the type signature. And since mapStateToProps returns an object, you can use ReturnType<typeof mapStateToProps> to get the type signature of that object. Combining these two with the '&' operator gives you your Props type. It's essentially a more concise (and less error-prone) way to do the exact same thing you were doing.

How to type mapDispatchToProps in a component props interface

I have a connected component with mapStateToProps and mapDispatchToProps and a connect HOC from react-redux, and I want to create type definitions for the component as briefly and as future proof as it is possible.
For mapStateToProps I can use ReturnType and it works fine:
interface Props extends ReturnType<typeof mapStateToProps> {};
class ConnectedComponent extends Component<Props> {
render() {
// `variableFromState` gets inferred here
console.log(this.props.variableFromState);
return (
// ...
);
}
}
const mapStateToProps = (state: ApplicationState) => ({
variableFromState: state.variable,
});
export default connect(mapStateToProps)(ConnectedComponent);
However for mapDispatchToProps I use a simple object, and I am not sure how to achieve the save effect with this, as I did for mapStateToProps
const mapStateToProps = (state: ApplicationState) => ({
// ...
});
const mapDispatchToProps = {
action1,
action2,
};
export default connect(mapStateToProps, mapDispatchToProps)(ConnectedComponent);
I am aware that I could use bindActionsCreators and then I could use the ReturnType, but I want to use a simple object here.
What I tried is to add the typeof <action> in the props interface, but it is still tedious to write for every component and every action.
interface Props extends ReturnType<typeof mapStateToProps> {
action1: typeof action1;
action2: typeof action2;
};
Also it is wrong when I use async thunk actions, because it infers the following for the action return value in the component, and not the value it really returns.
(dispatch: ThunkDispatch<any, null, AnyAction> & ThunkDispatch<any, undefined, AnyAction> & Dispatch<AnyAction>)
I have figured it out, how to achieve the effect I wanted with as less explicit typing as it is possible:
interface Props extends ConnectedProps<typeof connector> {};
class ConnectedComponent extends Component<Props> {
render() {
// Here actions, and state is inferred correctly with this method
return (
// ...
);
}
}
const mapStateToProps = (state: ApplicationState) => ({
variableFromState: state.variable,
});
const mapDispatchToProps = {
action1,
action2,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
export default connector(ConnectedComponent);

Redux Connect with Typescript - TS2347

I'm new to Typescript and though it is fascinating and a life-saver, this error is quite a hard nut to crack.
TypeScript error: Untyped function calls may not accept type arguments. TS2347
Can you please tell me what should be improved in the below class to get rid of this error ?
Here is the whole class
import React, { FunctionComponent } from 'react'
import { ListGroup } from 'react-bootstrap'
import { connect } from 'react-redux'
type StateProps = {
mbzArtists: IMBZArtist[],
releaseArtistID: string
}
type DispatchProps = {
findMBZReleases: (artistID: string) => void,
}
type OwnProps = {}
type MBZSearchResultsProps = StateProps & DispatchProps & OwnProps
const MBZSearchResults: FunctionComponent<MBZSearchResultsProps> = ({ findMBZReleases, mbzArtists, releaseArtistID }) => {
return (
<div className="MBZSearchResults">
// div content
</div>
)
}
const mapStateToProps = (state: AppState) => {
return {
mbzArtists: state.musicBrainz.mbzArtists,
releaseArtistID: state.musicBrainz.artistReleaseID
}
}
const mapDispatchToProps = (dispatch: any): DispatchProps => {
return {
findMBZReleases: (artistID: string) => dispatch(Actions.MBZActions.findMBZReleases(artistID))
}
}
export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(MBZSearchResults)
In case you require more information, please let me know.
Thanks.
The issue seems to be fixed by changing the export statement as below:
const component: React.FunctionComponent<OwnProps> =
connect(mapStateToProps, mapDispatchToProps)(MBZSearchResults)
export default component
Can I please have a comment from typescript users to let me know if this is the way to go ?
As stated from the error you posted.
TypeScript error: Untyped function calls may not accept type
arguments. TS2347
The connect function dosen't accept type arguments.
Change the export connect to this:
export default connect(mapStateToProps, mapDispatchToProps)(MBZSearchResults)

How to properly type a Redux connect call?

I am trying to use a Redux state store in combination with TypeScript. I am trying to use the official Typings of Redux and want to make the entire call on the connect method (which connects the mapStatetoProps and mapDispatchToProps with a component) type safe.
I usually see approaches where the methods mapStatetoProps and mapDispatchToProps are just custom typed and return a partial of the component props, such as the following:
function mapStateToProps(state: IStateStore, ownProps: Partial<IComponentProps>)
: Partial<IProjectEditorProps> {}
function mapDispatchToProps (dispatch: Dispatch, ownProps: Partial<IComponentProps>)
: Partial<IProjectEditorProps> {}
This is typed and works, but not really safe because it is possible to instantiate a component which misses props, as the usage of the Partial interface allows incomplete definitions. However, the Partial interface is required here because you may want to define some props in mapStateToProps and some in mapDispatchToProps, and not all in one function. So this is why I want to avoid this style.
What I am currently trying to use is directly embedding the functions in the connect call and typing the connect call with the generic typing supplied by redux:
connect<IComponentProps, any, any, IStateStore>(
(state, ownProps) => ({
/* some props supplied by redux state */
}),
dispatch => ({
/* some more props supplied by dispatch calls */
})
)(Component);
However, this also throws the error that the embedded mapStatetoProps and mapDispatchToProps calls to not define all Props each as both only require a subset of them, but together defining all Props.
How can I properly type the connect call so that the mapStatetoProps and mapDispatchToProps calls are really type safe and typing checks if the combined values defined by both methods supply all required props without one of the methods required to define all props at once? Is this somehow possible with my approach?
Option 1: Split IComponentProps
The simplest way to do this is probably just defining separate interfaces for your "state derived props", your "own props" and your "dispatch props" and then use an intersection type to join them together for the IComponentProps
import * as React from 'react';
import { connect, Dispatch } from 'react-redux'
import { IStateStore } from '#src/reducers';
interface IComponentOwnProps {
foo: string;
}
interface IComponentStoreProps {
bar: string;
}
interface IComponentDispatchProps {
fooAction: () => void;
}
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps
class IComponent extends React.Component<IComponentProps, never> {
public render() {
return (
<div>
foo: {this.props.foo}
bar: {this.props.bar}
<button onClick={this.props.fooAction}>Do Foo</button>
</div>
);
}
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
(state, ownProps): IComponentStoreProps => {
return {
bar: state.bar + ownProps.foo
};
},
(dispatch: Dispatch<IStateStore>): IComponentDispatchProps => (
{
fooAction: () => dispatch({type:'FOO_ACTION'})
}
)
)(IComponent);
We can set the connect function generic parameters like so:
<TStateProps, TDispatchProps, TOwnProps, State>
Option 2: Let your functions define your Props interface
Another option I've seen in the wild is to leveraging the ReturnType mapped type to allow your mapX2Props functions to actually define what they contribute to IComponentProps.
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps;
interface IComponentOwnProps {
foo: string;
}
type IComponentStoreProps = ReturnType<typeof mapStateToProps>;
type IComponentDispatchProps = ReturnType<typeof mapDispatchToProps>;
class IComponent extends React.Component<IComponentProps, never> {
//...
}
function mapStateToProps(state: IStateStore, ownProps: IComponentOwnProps) {
return {
bar: state.bar + ownProps.foo,
};
}
function mapDispatchToProps(dispatch: Dispatch<IStateStore>) {
return {
fooAction: () => dispatch({ type: 'FOO_ACTION' })
};
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
mapStateToProps,
mapDispatchToProps
)(IComponent);
The big advantage here is that it reduces a little bit of the boiler plate and makes it so you only have one place to update when you add a new mapped prop.
I've always steered away from ReturnType, simplify because it feels backwards to let your implementation define your programming interface "contracts" (IMO). It becomes almost too easy to change your IComponentProps in a way you didn't intend.
However, since everything here is pretty self contained, it's probably an acceptable use case.
One solution is to split your component properties into state-, dispatch- and maybe own-properties:
import React from "react";
import { connect } from "react-redux";
import { deleteItem } from "./action";
import { getItemById } from "./selectors";
interface StateProps {
title: string;
}
interface DispatchProps {
onDelete: () => any;
}
interface OwnProps {
id: string;
}
export type SampleItemProps = StateProps & DispatchProps & OwnProps;
export const SampleItem: React.SFC<SampleItemProps> = props => (
<div>
<div>{props.title}</div>
<button onClick={props.onDelete}>Delete</button>
</div>
);
// You can either use an explicit mapStateToProps...
const mapStateToProps = (state: RootState, ownProps: OwnProps) : StateProps => ({
title: getItemById(state, ownProps.id)
});
// Ommitted mapDispatchToProps...
// ... and infer the types from connects arguments ...
export default connect(mapStateToProps, mapDispatchToProps)(SampleItem);
// ... or explicitly type connect and "inline" map*To*.
export default connect<StateProps, DispatchProps, OwnProps, RootState>(
(state, ownProps) => ({
title: getItemById(state, ownProps.id)
}),
(dispatch, ownProps) => ({
onDelete: () => dispatch(deleteItem(ownProps.id))
})
)(SampleItem);
Really like #NSjonas's splitting approach, but I'd borrow something from his second approach as well to have a balance among practicality, not letting the implementation completely define your interface and not being extremely verbose in typing your dispatch actions;
import * as React from 'react';
import { connect, Dispatch } from 'react-redux'
import { IStateStore } from '#src/reducers';
import { fooAction } from '#src/actions';
interface IComponentOwnProps {
foo: string;
}
interface IComponentStoreProps {
bar: string;
}
interface IComponentDispatchProps {
doFoo: (...args: Parameters<typeof fooAction>) => void;
}
type IComponentProps = IComponentOwnProps & IComponentStoreProps & IComponentDispatchProps
class IComponent extends React.Component<IComponentProps, never> {
public render() {
return (
<div>
foo: {this.props.foo}
bar: {this.props.bar}
<button onClick={this.props.doFoo}>Do Foo</button>
</div>
);
}
}
export default connect<IComponentStoreProps, IComponentDispatchProps, IComponentOwnProps, IStateStore>(
(state, ownProps): IComponentStoreProps => {
return {
bar: state.bar + ownProps.foo
};
},
{
doFoo: fooAction
}
)(IComponent);

How to maintain action creator types in connected components with Redux, React and TypeScript?

I'm struggling to keep my actions in connected components type-safe.
Basically when I import a bunch of redux action creators, wrap them with the dispatcher using react-redux and pass them as props to a component, I'd like the resulting actions to maintain the original type information from the imported functions.
Actions have types and return type is inferred:
export const actionA = (p1: string, p2: number) =>
({ type: 'EXAMPLE_A', payload: { p1, p2 } })
export const actionB = (p1: number) =>
({ type: 'EXAMPLE_B', payload: p1 })
But my component still has some any types to satisfy the compiler, losing type safety.
import * as React from 'react'
import { Dispatch, bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as exampleActions from '../actions/example'
interface MyComponentProps {
someStore: SomeStoreState,
actions: any // <-- Just use whatever types exampleActions have?
}
class MyComponent extends React.Component<MyComponentProps, {}> {
constructor(props: MyComponentProps) {
super(props)
}
private foo() {
const { actionA } = this.props.actions.exampleActions
actionA('foo', 'bar') // <-- Compile time error pls
}
public render() {
return null
}
}
const mapStateToProps = (state: RootState) => ({
someStore: state.someStore
})
const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
actions: {
exampleActions: bindActionCreators<any>(exampleActions, dispatch)
}
})
export default connect (mapStateToProps, mapDispatchToProps)(MyComponent)
Declaring the function parameter types again in the props interface sorta helps, but I'd just like to maintain the original types so they are defined in one place.
I don't really care about the types within the dispatcher itself, so somehow casting the exampleTypes (and any other actions') type information to the props would be a good enough solution for me, as if the dispatch binding wasn't there at all and the creators themselves were passed as props.
Additionally, the application is using redux-promise-middleware, which means some actions may return promises. I'd also want that information to be preserved, so actions can be chained within the component. But I think with casting that shouldn't be an issue to begin with.
You need to explicitly type your action creators, then import their types along with the functions themselves. Creating some generic action interfaces can help with this, as typically I find the redux types themselves unhelpful. It is a little verbose, but the type support is often worth it, especially as you can get excellent typings within your reducers as well.
I typically use something like this for the actions/creators:
export interface TypedAction<TAction, TPayload> {
type: TAction;
payload: TPayload;
}
export type TypeA = "EXAMPLE_A";
export type TypeB = "EXAMPLE_B";
export interface PayloadA {
p1: string;
p2: number;
}
export interface PayloadB {
p1: number;
}
export type ActionA = TypedAction<TypeA, PayloadA>;
export type ActionB = TypedAction<TypeB, PayloadB>;
export type Actions = ActionA | ActionB;
export type ActionCreatorA = (p1: string, p2: number) => ActionA;
export type ActionCreatorB = (p1: number) => ActionB;
export const actionCreatorA: ActionCreatorA = (p1, p2) => ({
type: "EXAMPLE_A",
payload: {
p1,
p2
}
});
export const actionCreatorB: ActionCreatorB = (p1) => ({
type: "EXAMPLE_B",
payload: {
p1
}
});
Which can be used in a component as:
import * as React from 'react'
import { Dispatch, bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import {
actionCreatorA, ActionCreatorA,
actionCreatorB, ActionCreatorB
} from '../actions/example'
interface MyComponentProps {
someStore: SomeStoreState;
actionCreatorA: ActionCreatorA;
actionCreatorB: ActionCreatorB;
}
class MyComponent extends React.Component<MyComponentProps, {}> {
constructor(props: MyComponentProps) {
super(props)
}
private foo() {
const { actionA } = this.props;
actionA('foo', 'bar') // <-- Compiles
}
public render() {
return null
}
}
const mapStateToProps = (state: RootState) => ({
someStore: state.someStore
})
const mapDispatchToProps = {
actionCreatorA
};
export default connect (mapStateToProps, mapDispatchToProps)(MyComponent)
Reducers can also benefit by using:
import ( Actions } from "./actions/example";
// Actions here is the union type of all actions this reducer will
// handle, as exported from the actions file
export const someReducer = (state = defaultState, action: Actions) => {
switch (action.type) {
case "EXAMPLE_A":
// action is typed as ActionA
return {
p1: action.p1,
p2: action.p2
};
case "EXAMPLE_B":
// action is typed as ActionB
return {
p1: action.p1,
p2: action.p2 // <-- Does not compile, p2 does not exist on ActionB
};
default:
return state;
}
}

Resources