Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to properly type the reducer #195

Open
AdrienLemaire opened this issue Dec 5, 2018 · 5 comments
Open

how to properly type the reducer #195

AdrienLemaire opened this issue Dec 5, 2018 · 5 comments

Comments

@AdrienLemaire
Copy link

AdrienLemaire commented Dec 5, 2018

Related to #173

versions:

  • connected-react-router 5.0.1
  • react 16.6.3
  • react-router 4.4.0-beta.6
  • redux 4.0.1
  • react-redux 5.1.1

I'm trying to declare my root state but I keep getting errors on History which I haven't been able to resolve after hours.

reducer.ts

const persistConfig: PersistConfig = {
  key: "root",
  storage: localStorage,
};

export type RootState = ApiState &
  FirebaseState & {
    router: Reducer<RouterState, LocationChangeAction>;
  };

// connected-react-router v5 requires exporting a function accepting history
const createRootReducer: Reducer<RootState> = (history?: History) =>
  persistReducer(
    persistConfig,
    combineReducers({
      api,
      auth,
      firebase: persistReducer(
        {
          key: "firepersist",
          storage: localStorage,
          stateReconciler: hardSet,
        },
        firebaseReducer,
      ),
      router: connectRouter(history),
      ui,
      ...otherReducers,
    }),
  );

export default createRootReducer;

type check

$ tsc --project tsconfig.json
src/reducers/index.ts:52:7 - error TS2322: Type '(history?: History<any> | undefined) => Reducer<{ application: any; contact: any; faq: any; mediaList: any; releases: any; api: {}; auth: { isLoaded: boolean; isEmpty: boolean; userId: any; }; firebase: any; router: {}; ui: any; } & PersistPartial, AnyAction>' is not assignable to type 'Reducer<RootState, AnyAction>'.
  Types of parameters 'history' and 'state' are incompatible.
    Type 'RootState | undefined' is not assignable to type 'History<any> | undefined'.
      Type 'RootState' is not assignable to type 'History<any> | undefined'.
        Type 'RootState' is missing the following properties from type 'History<any>': length, action, location, push, and 7 more.

52 const createRootReducer: Reducer<RootState> = (history?: History) =>
         ~~~~~~~~~~~~~~~~~

src/reducers/index.ts:66:29 - error TS2345: Argument of type 'History<any> | undefined' is not assignable to parameter of type 'History<any>'.
  Type 'undefined' is not assignable to type 'History<any>'.

66       router: connectRouter(history),
                               ~~~~~~~


Found 2 errors.

error Command failed with exit code 1.

If I cannot solve this issue, I'll change Reducer<RootState> → Reducer and persistReducer(…) → persistReducer(…) as any, but this wouldn't be sane becore my exported RootState wouldn't guarantee that the expected state is correct.

I understand that history is undefined at the beginning. Do you know of a way to say "that's ok" this way ?

@damacisaac
Copy link

damacisaac commented Dec 11, 2018

Couple things that might help:

  • RootState["router"] should just be RouterState
  • I don't think you want an optional history when connecting the router. If this is related to server-side rendering then I would consider using entry points for server and browser.
  • Make sure you're using createBrowserHistory and using the appropriate History type.

@arnaud-zg
Copy link
Contributor

I'm not sure if it's the best solution, but it seems like combineReducers only want a function that returns a Reducer (given by redux). It works if I explicitly cast the result as Reducer like that:

router: connectRouter(history) as Reducer,

@kahurangitama
Copy link

I'm not sure if it's the best solution, but it seems like combineReducers only want a function that returns a Reducer (given by redux). It works if I explicitly cast the result as Reducer like that:

router: connectRouter(history) as Reducer,

Type 'Function' is not assignable to type 'Reducer<{}, AnyAction>'

@kahurangitama
Copy link

kahurangitama commented May 23, 2019

src/client/store/reducers.ts:17:3 - error TS2322: Type '<S>(reducer: Reducer<S, AnyAction>) => Reducer<S, AnyAction>' is not assignable to type 'Reducer<Reducer<{}, AnyAction>, AnyAction>'.
  Types of parameters 'reducer' and 'state' are incompatible.
    Type 'Reducer<{}, AnyAction> | undefined' is not assignable to type 'Reducer<{}, AnyAction>'.
      Type 'undefined' is not assignable to type 'Reducer<{}, AnyAction>'.

17   router: connectRouter(history),
     ~~~~~~

  src/client/store/reducers.ts:17:3
    17   router: connectRouter(history),
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    The expected type comes from property 'router' which is declared here on type 'ReducersMapObject<{ constructor: {}; toString: string; toLocaleString: string; valueOf: Object; hasOwnProperty: boolean; isPrototypeOf: boolean; propertyIsEnumerable: boolean; account: { isFetching: boolean; ... 9 more ...; addresses: null; } | { ...; } | { ...; } | { ...; }; ... 8 more ...; router: Reducer<...>; },...'

Please assist!

UPD: ended up with temporary solution by adding:

// @ts-ignore
router: connectRouter(history),

@favna
Copy link

favna commented May 26, 2019

The best way I ended up finding is to use the typesafe-actions library to property type both your actions and your reducer @kahurangitama.

You can find an example here: https://github.com/Favna/ReactTSDemo/blob/master/src/store

(note: also navigate to ProjectRoot/typings/typesafe-actions/index.d.ts which gets overwritten with the proper RootActions as per the lib's requirement for typesafe reducer)

Edit: since the sample code above lacks connected-react-router use this as stand in for the index.ts file from the linked folder:

import {
  connectRouter, go, goBack, goForward,
  push, replace, RouterState
} from 'connected-react-router';
// other imports

const routerActions = {
  push: typeof push,
  replace: typeof replace,
  go: typeof go,
  goBack: typeof goBack,
  goForward: typeof goForward,
};

export type ApplicationState = Readonly<{
  router: RouterState;
  app: DemoState;
}>;

export function* rootSaga () {
  yield all([fork(demoSaga)]);
}

export const rootActions = {
  router: routerActions,
  counter: counterActions,
};

export type RootAction = ActionType<typeof rootActions>;

export default (history: History) => combineReducers<ApplicationState>({
  router: connectRouter(history),
  app: DemoReducer,
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants