Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

type error with redux-thunk when using connect

I am using redux and redux-thunk with typescript. I am trying to inject to a component through connect() a simple thunk action creator, using mapDispatchToProps.

Actions

export enum TestActionTypes {
  THUNK_ACTION = "THUNK_ACTION"
}

export interface ThunkAction {
  type: TestActionTypes.THUNK_ACTION;
}

export type TestAction = ThunkAction;

Action Creators

export function thunkActionCreator() {
  return function(dispatch: Dispatch<any>) {
    dispatch({ type: TestAction.THUNK_ACTION });
  };

Connected Component

interface DemoScreenState {}

interface OwnProps {}

interface StateProps {}

interface DispatchProps {
  testThunk: () => void;
}

type DemoScreenProps = StateProps & DispatchProps & OwnProps;

class DemoScreen extends React.Component<
  DemoScreenProps,
  DemoScreenState
> {
  constructor(props: DemoScreenProps) {
    super(props);
  }

  componentDidMount() {
    this.props.testThunk();
  }

  render() {
    return null;
  }
}

function mapStateToProps(state: any): StateProps {
  return {};
}

function mapDispatchToProps(dispatch: Dispatch<any>): DispatchProps {
  return {
    testThunk: () => dispatch(thunkActionCreator())
  };
}

export default connect<StateProps, DispatchProps, OwnProps>(
  mapStateToProps,
  mapDispatchToProps
)(DemoScreen);

Store

 import { createStore, applyMiddleware } from "redux";
 import rootReducer from "./RootReducer";
 import thunk from "redux-thunk";

 const store = createStore(rootReducer, applyMiddleware(thunk));

 export default store;

However, I am encountering two issue when using connect(). First of all, I get a type error for the declaration of testThunk in mapDispatchToProps.

Argument of type '(dispatch: Dispatch) => void' is not assignable to parameter of type 'Action'. Property 'type' is missing in type '(dispatch: Dispatch) => void'. I don't know how to handle this, as by definition a thunk is different from a plain action.

* EDIT 24/10/2018 * cf answer below, using:

"@types/react-redux": "^6.0.9",
"react": "16.3.1",
"react-native": "~0.55.2",
"react-redux": "^5.0.7",
"redux": "^4.0.1",
"redux-thunk": "^2.3.0"
like image 678
vbvx Avatar asked May 11 '18 14:05

vbvx


People also ask

Which function should be used to wrap Redux thunk?

1. dispatch: It is a method used to dispatch actions, that can be received by reducers. 2. getState: It gives access to store inside the thunk function.

Why is redux thunk better than Redux?

But on the other hand, for bigger projects, Redux-Thunk may sometimes get you in trouble, as it can be hard to scale if your side effect or asynchronous logic increases, whereas in the case of Redux-Saga, it comes power-packed with some amazing things such as concurrent side effects, canceling side effects, debouncing ...

How does Redux thunk Change Action creators?

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.


1 Answers

I finally managed to get a properly typed connected component, mainly using this as a starting point https://github.com/reduxjs/redux-thunk/blob/master/test/typescript.ts for proper async action creator typing. The key is to properly type your store, and to use ThunkDispatch and ThunkAction from redux-thunk.

Store.tsx

import { createStore, applyMiddleware, combineReducers } from "redux";
import { playground, IState } from "./reducer";
import thunk, { ThunkMiddleware } from "redux-thunk";
import { IActions } from "./actions";

export interface RootState {
  playground: IState;
}

export type RootActions = IActions;

const rootReducer = combineReducers({ playground });

const store = createStore<RootState, RootActions, {}, {}>(
  rootReducer,
  applyMiddleware(thunk as ThunkMiddleware<RootState, RootActions>)

Actions.tsx

import { Action } from "redux";
import { ThunkAction } from "redux-thunk";
import { RootState, RootActions } from "./store";

type ThunkResult<R> = ThunkAction<R, RootState, undefined, RootActions>;

//Actions
export enum TestActionTypes {
  THUNK_ACTION = "THUNK_ACTION",
}

export interface AsyncAction extends Action {
  type: TestActionTypes.THUNK_ACTION;
}

export type IActions = AsyncAction;

//Action creator
export function testAsyncActionCreator(): ThunkResult<void> {
  return (dispatch, getState) => {
    console.log(getState);
    dispatch({ type: TestActionTypes.THUNK_ACTION });
  };
}

Reducer.tsx

import { IActions, TestActionTypes } from "./actions";
import { Reducer } from "redux";

export interface IState {}

const defaultValue: IState = {};

export const playground: Reducer<IState, IActions> = (
  state = defaultValue,
  action
) => {
  switch (action.type) {
    default:
      return state;
  }
};

ConnectedComponent.tsx

...
import { connect } from "react-redux";
import { RootState, RootActions } from "./redux/store";
import { syncActionCreator, testGetState } from "./redux/actions";
import { ThunkDispatch } from "redux-thunk";

interface OwnProps {}

interface StateProps {}

interface DispatchProps {
  test: () => void;
}

type Props = OwnProps & StateProps & DispatchProps;

class DumbContainer extends React.Component<Props> {
  render() {
    return (
      ...
    );
  }
}


const mapStateToProps = (state: RootState): StateProps => {
  return {
    text: state.playground.text
  };
};

const mapDispatchToProps = (
  dispatch: ThunkDispatch<RootState, undefined, RootActions>
): DispatchProps => {
  return {
    test: () => {
      dispatch(testAsyncActionCreator());
    }
  };
};

export default connect<StateProps, DispatchProps, OwnProps, RootState>(
  mapStateToProps,
  mapDispatchToProps
)(DumbContainer);

I also have a playground project here https://github.com/vbvxx/playground-reactnative-typescript

like image 126
vbvx Avatar answered Oct 18 '22 05:10

vbvx