Related to https://github.com/supasate/connected-react-router/issues/173
versions:
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 ?
Couple things that might help:
RootState["router"] should just be RouterStatecreateBrowserHistory and using the appropriate History type.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,
I'm not sure if it's the best solution, but it seems like
combineReducersonly want a function that returns aReducer(given byredux). It works if I explicitly cast the result asReducerlike that:router: connectRouter(history) as Reducer,
Type 'Function' is not assignable to type 'Reducer<{}, AnyAction>'
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),
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,
});
Most helpful comment
I'm not sure if it's the best solution, but it seems like
combineReducersonly want a function that returns aReducer(given byredux). It works if I explicitly cast the result asReducerlike that: