piotrwitek / typesafe-actions

Typesafe utilities for "action-creators" in Redux / Flux Architecture
https://codesandbox.io/s/github/piotrwitek/typesafe-actions/tree/master/codesandbox
MIT License
2.41k stars 99 forks source link

I'm receiving a type error when using two async actions on one reducer #233

Closed brunoti closed 4 years ago

brunoti commented 4 years ago

Description

I'm receiving a type error when using two async actions on one reducer:

Argument of type '(response: AxiosResponse<LoginResponse>) => PayloadAction<"USER/AUTH/RENEW_SUCCESS", AxiosResponse<LoginResponse>>' is not assignable to parameter of type '((...args: any[]) => PayloadAction<"USER/PERMISSION/SET_LOCATION", PermissionInfo> | PayloadAction<"USER/PERMISSION/SET_LOADING", boolean> | ... 11 more ... | EmptyAction<...>) | ((...args: any[]) => PayloadAction<...> | ... 12 more ... | EmptyAction<...>)[]'.
  Type '(response: AxiosResponse<LoginResponse>) => PayloadAction<"USER/AUTH/RENEW_SUCCESS", AxiosResponse<LoginResponse>>' is not assignable to type '(...args: any[]) => PayloadAction<"USER/PERMISSION/SET_LOCATION", PermissionInfo> | PayloadAction<"USER/PERMISSION/SET_LOADING", boolean> | ... 11 more ... | EmptyAction<...>'.
    Type 'PayloadAction<"USER/AUTH/RENEW_SUCCESS", AxiosResponse<LoginResponse>>' is not assignable to type 'PayloadAction<"USER/PERMISSION/SET_LOCATION", PermissionInfo> | PayloadAction<"USER/PERMISSION/SET_LOADING", boolean> | ... 11 more ... | EmptyAction<...>'.
      Type 'PayloadAction<"USER/AUTH/RENEW_SUCCESS", AxiosResponse<LoginResponse>>' is not assignable to type 'PayloadAction<"USER/PERMISSION/SET_LOCATION", PermissionInfo>'.
        Type '"USER/AUTH/RENEW_SUCCESS"' is not assignable to type '"USER/PERMISSION/SET_LOCATION"'.

Mandatory info

declare module 'typesafe-actions' { export type Store = StateType<typeof import('./index').store>; export type RootState = StateType<typeof import('./root-reducer').reducer>; export type RootAction = ActionType<typeof import('./root-action')>;

interface Types { RootAction: RootAction; } }


- [x] Did you checked [compatibility notes][1] and [migration guides][2]?

## How to Reproduce

This is my folder structure:

./app/store ├── clients.ts ├── index.ts ├── root-action.ts ├── root-reducer.ts └── types.d.ts

./app/basket/store ├── actions.ts ├── order │   ├── actions.ts │   └── reducer.ts ├── product │   ├── actions.ts │   └── reducer.ts ├── reducer.ts └── search ├── actions.ts └── reducer.ts ./app/user/store ├── actions.ts ├── auth │   ├── actions.ts │   ├── reducer.ts │   └── thunks.ts ├── permission │   ├── actions.ts │   └── reducer.ts └── reducer.ts


```ts
import { createReducer } from 'typesafe-actions';
import { UserResponse } from 'user/types';
import * as actions from './actions';

type UserAuthState = Readonly<{
  data?: UserResponse;
  loading: boolean;
  token?: string;
}>;

const INITIAL_STATE: UserAuthState = {
  data: undefined,
  token: undefined,
  loading: false,
};

const reducer = createReducer(INITIAL_STATE)
  .handleAction(actions.login.success, (state, { payload }) => ({
    ...state,
    data: payload.data.user,
    token: payload.data.token,
    loading: false,
  }))
  .handleAction(actions.login.request, state => ({
    ...state,
    loading: true,
  }))
  .handleAction(actions.login.failure, state => ({
    ...state,
    loading: false,
  }))
  .handleAction(actions.setUser, (state, { payload }) => ({
    ...state,
    data: payload,
  }))
  .handleAction(actions.setToken, (state, { payload }) => ({
    ...state,
    token: payload,
  }))
  .handleAction(actions.renew.success, (state, { payload }) => ({ // Here I receive the error descripted
    ...state,
    data: payload.data.user,
    token: payload.data.token,
  }))
  .handleAction(actions.reset, () => ({ ...INITIAL_STATE }));

export { reducer };

My async actions (./app/user/store/auth):

export const renew = createAsyncAction(
  ['USER/AUTH/RENEW', (): RequestPayload => ({
    client: 'user',
    request: {
      method: 'POST',
      url: '/renew',
    },
  })],
  ['USER/AUTH/RENEW_SUCCESS', (response: AxiosResponse<LoginResponse>): AxiosResponse<LoginResponse> => response],
  ['USER/AUTH/RENEW_FAIL', (error: AxiosError): AxiosError => error],
)<RequestPayload, AxiosResponse<LoginResponse>, AxiosError>();

export const renew = createAsyncAction(
  ['USER/AUTH/RENEW', (): RequestPayload => ({
    client: 'user',
    request: {
      method: 'POST',
      url: '/renew',
    },
  })],
  ['USER/AUTH/RENEW_SUCCESS', (response: AxiosResponse<LoginResponse>): AxiosResponse<LoginResponse> => response],
  ['USER/AUTH/RENEW_FAIL', (error: AxiosError): AxiosError => error],
)<RequestPayload, AxiosResponse<LoginResponse>, AxiosError>();

export const login = createAsyncAction(
  ['USER/AUTH/LOGIN', (data: Login): RequestPayload => ({
    client: 'user',
    request: {
      method: 'POST',
      url: '/login',
      data,
    },
  })],
  ['USER/AUTH/LOGIN_SUCCESS', (response: AxiosResponse<LoginResponse>): AxiosResponse<LoginResponse> => response],
  ['USER/AUTH/LOGIN_FAIL', (error: AxiosError): AxiosError => error],
)<RequestPayload, AxiosResponse<LoginResponse>, AxiosError>();

The action the error cites as conflict (./app/user/store/permissions/actions.ts):

import { createAction } from 'typesafe-actions';
import { PermissionInfo } from 'expo-permissions';

export const setLocation = createAction('USER/PERMISSION/SET_LOCATION')<PermissionInfo>();
export const setLoading = createAction('USER/PERMISSION/SET_LOADING')<boolean>();

Expected behavior

No type error there, since everything was ok until I added the renew handlers on this reducer. I've tried a lot of stuff, changing types, removing that other action but the error just changes the message and never goes away.

Suggested solution(s)

I really don't know. But if I can help in any way, just say. I'm sorry I couldn't put a codesandbox link. I'm using ReactNative and I found it very hard to recreate the same environment on codesandbox.

Project Dependencies

Environment (optional)

piotrwitek commented 4 years ago

Hey @brunoti,

When declaring async actions there are 2 typing styles, please make sure to not use both of them. Here are example docs: #143

Basically try this:

export const renew = createAsyncAction(
  ['USER/AUTH/RENEW', (): RequestPayload => ({
    client: 'user',
    request: {
      method: 'POST',
      url: '/renew',
    },
  })],
  ['USER/AUTH/RENEW_SUCCESS', (response: AxiosResponse<LoginResponse>): AxiosResponse<LoginResponse> => response],
  ['USER/AUTH/RENEW_FAIL', (error: AxiosError): AxiosError => error],
)();

Please do the same with all async actions respectively.

brunoti commented 4 years ago

That worked! I got confused between docs in the beginning. That plus tutorials got me to the wrong path.

piotrwitek commented 4 years ago

Yes it's confusing right now I agree, I have to work on it.