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

v5.1.0 Reducer state type declaration reporting unknown when use ReturnType<typeof myCombinedReducer> #210

Closed dcs3spp closed 4 years ago

dcs3spp commented 4 years ago

Description

Hi,

Not sure if this is a bug or my usage error....

I have recently upgraded to v5.1.0 of the library and when declaring the type for the state in a reducer the resulting properties are defined as unknown.

Steps to Reproduce

export type ErrorState = ReturnType<typeof errorsReducer>;
//properties of ErrorState are unknown

I have uploaded an example screenshot of reducer code, here, to illustrate what is happening..... The underlines for state and action are linter warnings for usage of any type.

I am defining the action using the new createAction syntax as follows:

export const notifyErrorAction = createAction(
  constants.NOTIFY_ERROR,
  // payload creator function, no meta data function specified
  (
    raisingAction: string,
    info: Error,
    sourceComponent: string,
  ): NotifyError => ({
    raisingAction: raisingAction,
    info: info,
    sourceComponent: sourceComponent,
  }),
)<NotifyError>();

Expected behavior

Properties in reducer state type declaration are typed similar to behaviour in version 4.4.2.

Project Dependencies

Environment (optional)

piotrwitek commented 4 years ago

Hi @dcs3spp, Thanks for report.

Mandatory info:

  1. Are you extending internal types to enable type-free syntax with createReducer? (Check the docs here if you're not sure)

Also please paste full reducer code, image is not complete working code.

dcs3spp commented 4 years ago

Hi @piotrwitek, Thanks for responding :) Yes, I have a type declaration file for RootAction, RootState etc. as follows:

declare module 'ReduxTypes' {
  import { StateType, ActionType } from 'typesafe-actions';
  export type Store = StateType<
    typeof import('../app/redux/store/index').default
  >;

  export type RootState = StateType<
    ReturnType<typeof import('../app/redux/store/rootReducer').default>
  >;

  export type RootAction = ActionType<
    typeof import('../app/redux/store/rootAction').default
  >;

  export type Services = typeof import('../app/services').default;
}

where the rootAction.ts file is defined as....

import * as errorActions from '../features/error/actions'; 
import * as courseActions from '../features/course/actions';

export default {
  errors: errorActions,
  courses: courseActions,
};

Full reducer code is:

import { combineReducers } from "redux";
import { createReducer } from "typesafe-actions";

import { ErrorReport } from "./types";
import { clearErrorAction, notifyErrorAction, ClearError } from "./actions";

/**
 * Initial State
 */
const initialState: ErrorReport[] = [];

/**
 * Helper functions to:
 * - addError: Add an error report to state
 * - removeError: Remove error report from state
 */
const addError = (
  state: ErrorReport[],
  newItem: ErrorReport
): ErrorReport[] => {
  if (state.find(item => item.sourceComponent === newItem.sourceComponent)) {
    // if there is already an error report for source map then replace it
    return state.map(item => {
      if (item.sourceComponent === newItem.sourceComponent) {
        return newItem;
      }
      return item;
    });
  } else {
    // create a new array with the new item appended
    return state.concat([newItem]);
  }
};

const removeError = (
  state: ErrorReport[],
  newItem: ClearError
): ErrorReport[] => {
  // return an array that is filtered without including sourcecomponent
  const newList: ErrorReport[] = state.filter(
    item => item.sourceComponent !== newItem.sourceComponent
  );

  console.log(
    `[Error reducer] removeError => ${JSON.stringify(newList, null, 2)}`
  );
  return newList;
};

const error = createReducer(initialState as ErrorReport[])
  .handleAction([notifyErrorAction], (state, action): ErrorReport[] =>
    addError(state, action.payload)
  )
  .handleAction([clearErrorAction], (state, action): ErrorReport[] =>
    removeError(state, action.payload)
  );

const errorsReducer = combineReducers({
  error
});

/**
 * Exports
 */
export default errorsReducer;

export type ErrorState = ReturnType<typeof errorsReducer>;

The issue still happens if I use the createAction function from deprecated. In the meantime, going to revert back to 4.4.2.

piotrwitek commented 4 years ago

Compare this:

Guide:

// types.d.ts
import { StateType, ActionType } from 'typesafe-actions';

export type RootAction = ActionType<typeof import('./actions').default>;

declare module 'typesafe-actions' {
  interface Types {
    RootAction: RootAction;
  }
}

Your code:

declare module 'ReduxTypes' {
  import { StateType, ActionType } from 'typesafe-actions';
  export type Store = StateType<
    typeof import('../app/redux/store/index').default
  >;

  export type RootState = StateType<
    ReturnType<typeof import('../app/redux/store/rootReducer').default>
  >;

  export type RootAction = ActionType<
    typeof import('../app/redux/store/rootAction').default
  >;

  export type Services = typeof import('../app/services').default;
}

Do you see difference?

You need to fix above to make it work.

piotrwitek commented 4 years ago

Here is the best example from codesanbox: https://github.com/piotrwitek/typesafe-actions/blob/master/codesandbox/src/store/types.d.ts

I'm working on a new tutorial, so this section will be improved.

dcs3spp commented 4 years ago

Many thanks @piotrwitek, that solved it. I changed type declaration file in my src/@types folder to be:

import { StateType, ActionType } from 'typesafe-actions';

declare module 'typesafe-actions' {
  export type Store = StateType<
    typeof import('../app/redux/store/index').default
  >;

  export type RootState = StateType<
    ReturnType<typeof import('../app/redux/store/rootReducer').default>
  >;

  export type RootAction = ActionType<
    typeof import('../app/redux/store/rootAction').default
  >;

  export type Services = typeof import('../app/services').default;

  interface Types {
    RootAction: RootAction;
  }
}

Not sure why, but it solved the issue..... Is it something to do with the above type declaration module overriding those in node_modules/typesafe-actions type declarations? Is the Types interface referenced in createReducer code....?

Anyway, many thanks again that solved the issue, appreciated :)

piotrwitek commented 4 years ago

@dcs3spp correct it's referenced in createReducer code :) I'm glad it helped to fix your issue!

dcs3spp commented 4 years ago

👍 Many thanks again for your help @piotrwitek. typesafe-actions library a valuable asset for typescript redux developers :)