-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Describe inferring RootState type from store.getState #324
Comments
That works indeed! I just arrived at that section of the documentation this morning. Here are my results with TypeScript 3.7.5: It fails with Based on my experience, I would recommend to leave out BTW: Redux Toolkit has been really helpful so far. Thanks a lot for another massive time-saver! |
@firstred : ah.... I'm very confused. Inferring the return type of If I hover over const rootReducer: Reducer<CombinedState<{
issuesDisplay: CurrentDisplayState;
repoDetails: RepoDetailsState;
issues: IssuesState;
comments: CommentsState;
}>, AnyAction> Where and how are you seeing problems with this? |
Can you put together a project that reproduces this, either as a CodeSandbox or a separate repo? |
Found the issue! I hadn't understood from the documentation that I was supposed to import Back the issues described in the OP: that works for me now. How about improving these particular examples some more by showing that you have to import from |
Hmm. It's the exact same function - RTK just does There shouldn't be any actual difference in behavior or types at all. |
Mind = blown haha! |
Indeed, it still works: https://codesandbox.io/s/rtk-github-issues-example-03-final-co03n I am unable to import the specific commit into CodeSandbox, but here's a link to it: https://github.com/firstred/bunqDesktop/blob/f514eb8f60e1f26cc997f4befaa9c51a4f65e422/src/store/index.ts |
Maybe you have an older version of redux installed than the one re-exported from RTK? |
Yep, I bet that's it. Looking at That would likely end up with both |
Ahh, good catch! That was the issue. Didn't realize RTK needed 4.0.0 or higher. Thanks everyone! |
All that said, I'd still like to have the original described |
Yes, back to the original issue. I now finally have the right tools installed to confirm that it is indeed better if you prefer use I'll create a PR! |
Such changes would conflict with this advice from docs on const store = configureStore({
reducer: rootReducer,
middleware: [
...customMiddleware,
...getDefaultMiddleware<RootState>(),
] as const,
})
// Type alias 'RootState' circularly references itself. ts(2456)
export type RootState = ReturnType<typeof store.getState> |
Indeed, thanks for pointing that out @amankkg! We should probably advise against passing the reducers directly to |
I have added a warning: fb5dd97 |
Recently I was struggling with circular references problem in my code. I have read an article that proposes a solution https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de but TBH in my opinion a tick with "internal" module could add a lot of noise to the code. The only reasonable solution I have found so far is to move type definitions to a separate module, for example types.tx export type RootState = {
todos: Todo[];
}; ...which IMHO is not that bad because it works like kind of documentation that clearly describes a shape of the app state. In the "sub-reducers" todos/reducer.tsx const initialState: RootState["todos"] = [];
export const reducer = createReducer(initialState, ....); @markerikson Some exemplary code that illustrates the problemstore.ts import { rootReducer } from "./store/rootReducer";
export store = createStore({
reducer: rootReducer
});
export RootState = ReturnType<typeof store.getState>;
export type AppThunk = ThunkAction<void, RootState, unknown, Action<string>>; store/rootReducer.ts import { reducer as todosReducer } from "./todos/reducer";
export rootReducer = combineReducers({
todos: todosReducer
}) store/todos/reducer.ts const initialState = [];
export reducer = createReducer(initialState, builder =>
builder
.addCase(fetchTodos.fulfilled, (draft, { payload: todos }) => todos)
.addCase(updateTodo.fultilled, (draft, { payload: { id, tod } }) => [...])
); store/counter/actions.ts import { AppThunk } from '../store'; // TODO: this import causes circular reference problem
export const updateTodo = createAsybcThunk(...);
export const completeAll = (): AppThunk => (dispatch, getState) => {
return Promise.all(getState().todos.map(todo => {
return dispatch(updateTodo({ id: todo.id, { ...todo, completed: true } }));
}));
} |
Doing export RootState = ReturnType<typeof rootReducer>; should break your circular reference more early. For TS, it doesn't so much matter if the modules depend on each other. So importing files in a circle is no problem. It only matters when your actual types depend on each other circularly. |
Yep. The TS compiler can handle circular references fine. Export a slice reducer, import into the root reducer, export the root state type, import the state type into the slice, and it works fine. It's actual runtime circular references that cause problems. |
Got it, thank you for the explanation! You're right, in my case it wasn't a problem with circular imports. Here everything is fine but as soon as I add It happens because Unfortunately the same problem strikes again if I remove the selector from the thunk and manually specify store's type in async thunk config, like: export const multiplyBy = createAsyncThunk<
number,
number,
{
state: RootState;
}
>("counter/multiplyBy", (by, { getState }) => {
const value = getState().counter.value;
return value * by;
}); I'm not a TypeScript expert but I guess that it happens because RootState depends on rootReducer, which depends on the slice, which uses thunk that uses a selector (or has RootState typing). So I'm going to raise the same question again - Is there a workaround for this? |
Having gone through the docs recently myself, I think it could be useful to mention how to properly get the typings for state when doing code-splitting with dynamically loaded reducers, using |
@lucassus Here's a forked codesandbox from yours utilizing both methods shown in the docs to define |
@msutkowski Thank you very much! You saved me quite a few hours of debugging ;) @markerikson Why in this case these brackets are so important? |
Per @lucassus, using This is pretty subtle and unintuitive. It's also unfortunate that some of the examples on the Usage with Typescript page use the @msutkowski Would it be worth updating the examples on that page, and explicitly mentioning this specific behavior? |
Switching the builder syntax doesn't resolve the circular reference warning for me, but export type RootState = ReturnType<typeof rootReducer>; Does. I'm using // using combineReducers
const allReducers = combineReducers({
auth: AuthReducer,
})
// a persistence library
const persistedReducer = persistReducer(persistConfig, allReducers);
import { someMiddleware } from '../middleware/something';
const customizedMiddleware = getDefaultMiddleware({
serializableCheck: {
ignoredActions: [some, things, here],
}
}).concat(
someMiddleware,
)
const store = configureStore({
reducer: persistedReducer, // the combined reducer
middleware: customizedMiddleware,
})
// Doesn't complain:
export type RootState = ReturnType<typeof persistedReducer>
// Circular reference complaint:
// export type RootState = ReturnType<typeof store.getState>
/// --- in a slice somewhere ---
import type { RootState } from '../store/store'
const someSlice = createSlice({
// ...
extraReducers: builder => {
builder.addCase(LogIn.fulfilled, (state, action: PayloadAction<any>) => {
// ...
})
}
})
|
Can someone summarize the changes that would be useful at this point? Sounds like one thing might be to use the ES6 inline object syntax ( |
any solution to fix circularly references ? |
@dmitryshelomanov this is extremely specific regarding the individual code. Some examples were given in this issue, but nobody will be able to tell you what works for you without you either trying it out or giving a reasonable reproduction. |
I was having However, I found that passing the reducers (from
|
The docs show inferring
type RootState = ReturnType<typeof rootReducer>
. But, if you pass the slice reducers directly toconfigureStore()
, you never created the root reducer yourself, so you need to infer it astype RootState = ReturnType<typeof store.getState>
instead.We should update the Advanced Tutorial and Usage with TypeScript pages to mention that.
See https://stackoverflow.com/a/59827580/62937 for an example of this
The text was updated successfully, but these errors were encountered: