I have a configured project with React, Redux, Immutable.js + TypeScript. During implementation I was trying to declare types as much as possible and found interesting issue. See code example below:
Short configuration of redux store
import { createStore } from 'redux';
import { combineReducers } from 'redux-immutable';
const rootReducer = combineReducers({...});
const store = createStore(rootReducer);
Somewhere inside component
// ...
const mapStateToProps = (state: ReturnType<typeof rootReducer>) => {
// state is plain object :(
};
On state hover in VS Code, tooltip shows that state is a plain object, however it is not. It should be a custom collection from Immutable.js
How can I get a correct type of rootReducer? Or what I am doing wrong?
Screenshots:
P.S. StateType and ReturnType do the same stuff
I spent a lot of time on finding a solution of how to make TypeScript work with Immutable.js in pair and unfortunately there is no explicit/trivial solution (or I didn't find it).
If you on the start of your project and looking for some alternatives I would highly recommend you to take a look at the Immer library. It provides mostly the same functionality and perfectly supports TypeScript typings.
This works (no need for Immutable.js):
export type RootState = ReturnType<typeof rootReducer>;
If for some reason it does not works, this one should work:
export type RootState = ReturnType<typeof store.getState>
Related
I'm using TypeScript in conjunction with Redux.
I'm following the docs and they don't export the store, only the two types mentioned below:
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
I need to use the store in the Provider, should I export and import the store as usual or there is another way to do it when using TypeScript?
Those are the steps for additional code you have to write for using TypeScript. It only is about types you have to write, not about runtime code. So you have to follow the normal quick start tutorial just as well - meaning: yes, you also have to export the store or you will not be able to ever interact with it.
I've been reading the documentation on using Redux with Typescript and I think I've got everything right, but my compiler is complaining regardless.
This is the problematic line:
// Widget.tsx
const error = useTypedSelector(state => state.error.widgetError)
// ^
// Object is of type 'unknown'. TS2571
useTypedSelector is a recommended by the docs custom hook, so you won't have to typecast state as RootState whenever you use a selector.
// store.ts
export type RootState = ReturnType<typeof store.getState>
// hooks.ts
import { TypedUseSelectorHook, useSelector } from 'react-redux'
import { RootState } from './store'
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector
I don't understand what the problem is. My entire state tree is typed, and so is the state argument in the callback. Hovering over the object in VS Code confirms this.
I've tried all sorts of typecasting to get around it, I still get the same error.
const error = useSelector((state: RootState) => state.error.widgetError) // no
const error = useSelector<RootState, string | null>(state => state.error.widgetError) // nup
const error = useTypedSelector(state => (state as RootState).error.widgetError) // nuh-uh
The only thing that worked was adding #ts-ignore, but that kind of defeats half the purpose of using typescript to begin with.
I'm out of both smart and stupid ideas at this point. Halp please?
I can't reproduce this issue on codesandbox, nor in a new react app with the typescript template. It is most likely caused by (incorrectly?) adding ts to an existing js react app.
If RootState is defined then const error = useSelector<RootState, ErrorState>((state) => state.error).widgetError; will do the job
import { configureStore } from "#reduxjs/toolkit";
import testSlice from "./testSlice";
import {combineReducers} from "redux";
const rootReducer = combineReducers({test: testSlice})
export const store = configureStore({
reducer: rootReducer,
});
export type RootState = ReturnType<typeof rootReducer>
This is my current workaround in order to get the type of my root reducer. I'm reading the docs and I can't seem to find how to get my root reducer from the store if I was using slices to create the reducer in my configure store. It's just disappointing that I have to use combineReducers again while using redux-toolkit just to get my rootReducer type.
I'm looking for a code like this:
import { configureStore } from "#reduxjs/toolkit";
import testSlice from "./testSlice";
import userSlice from "./userSlice";
export const store = configureStore({
reducer: {test: testSlice, user: userSlice},
});
export type RootState = ReturnType<typeof store.getReducer()>
A better type for RootState would be as below:
export type RootState = ReturnType<typeof store.getState>
This implementation is included in the document. Please take time to read it.
If your intention of getting the rootReducer isn't only for defining the RootState type, you can simply named-export it from store file and then import it in your desired file to use.
// store.js
// ...
export const rootReducer = combineReducers({test: testSlice});
It's not a common and practical usage for the rootReducer to be imported and consumed in other places but the store file, where it will be used to configure the store object.
Therefore, I think there's no point for Redux Toolkit to provide a way to access the rootReducer.
You don't need to
Huan's answer is spot-on. It is very unlikely that you will actually need to access the root reducer type. You can get the state type without it. Their answer is the best solution to underlying problem.
But if you want to...
If anyone reading this question would like to know how to derive the root reducer type from the store type, it can be done by using TypeScript conditional types. This is the same sort of logic that you see in the redux source code.
We can define a reusable utility type that gets the reducer type from the type of the store. It does this by accessing the types for the state S and the action A, which are both generic type parameters of the redux Store<S, A> type. Then we apply those same generics to the Reducer<S, A> type.
// Can be imported from 'redux' or from '#reduxjs/toolkit', both are the same.
import { Reducer, Store } from '#reduxjs/toolkit';
type ReducerFromStore<T> = T extends Store<infer S, infer A> ? Reducer<S, A> : never
You can use this utility type to get the root reducer type from a specific store instance.
export const store = configureStore({
reducer: {test: testSlice, user: userSlice},
});
export type RootReducer = ReducerFromStore<typeof store>;
Note: The exact type of the store object created by configureStore is the redux toolkit EnhancedStore type. But this extends the core redux Store type, so no special handling is needed.
I am using the article of Dan Abramov to sync my redux store with browser LocalStorage.
The problem is I am using typescript and when I try to create the store the following way:
const store = createStore(
App,
persistedState
);
(where persistedState is an object type of IStoreState | undefined) then typescript throws an error:
Type 'IStoreState' is not assignable to type 'StoreEnhancer<unknown, unknown>'.
My question is how can I transform my state to StoreEnhancer to succesfully pass it to createStore function?
Your error actually means that createStore is matching the wrong overload. The second argument to createStore can be either an enhancer or a preloadedState.
export interface StoreCreator {
<S, A extends Action, Ext, StateExt>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S & StateExt, A> & Ext
<S, A extends Action, Ext, StateExt>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
enhancer?: StoreEnhancer<Ext>
): Store<S & StateExt, A> & Ext
}
In this case it is a preloadedState and should be assignable to PreloadedState<IStoreState> which is basically a deep partial of IStoreState. Perhaps you can fix the error by tweaking the return types on your loadState() function, but I have a better solution.
You can avoid the confusion about which overload to match by using the configureStore utility from Redux Toolkit. You set your persistedState as the preloadedState property of the store config. This property is optional so undefined is okay.
const store = configureStore({
reducer: App, // can be a reducer or a reducer map object
preloadedState: persistedState,
});
You might want to look into the redux-persist package which implements this idea with lots of configuration options.
I am trying to learn redux and I am trying to implement the redux-thunk middleware. I've been following a few different tutorials and they suggest something similar to this:
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";
...
const middleware = applyMiddleware(promise(), thunk);
const store = createStore(reducers, middleware);
This gives me the following error:
/Users/me/Documents/workspace/redux/node_modules/redux-thunk/index.d.ts
(4,47): Generic type 'Dispatch' requires 2 type argument(s).
Can someone please explain what is going on and how to fix this?
Many thanks
This problem turns out to be related to the new version of redux (4.0.0) being incompatible with the current version of redux-thunk (2.2.0).
See this link: https://github.com/gaearon/redux-thunk/issues/169
and this PR: https://github.com/gaearon/redux-thunk/pull/180
I think you need to type dispatch in a way:
interface IStoreState {
readonly pendingActions: number;
readonly isAuthenticated: boolean;
};
function signIn(): (dispatch: Dispatch<IStoreState>) => ....
Related article.