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

What to do to replace removed PreloadedState type in v2? #3946

Closed
xsjcTony opened this issue Dec 5, 2023 · 6 comments
Closed

What to do to replace removed PreloadedState type in v2? #3946

xsjcTony opened this issue Dec 5, 2023 · 6 comments

Comments

@xsjcTony
Copy link

xsjcTony commented Dec 5, 2023

Usecase: I have a createStore utility function that takes the preloadedState as parameter, and pass it to configureStore, for generating the desired redux store state in testing.

As you can see in the code snippet below, what should I use now to replace PreloadState<RootState>?
Based on the migration guide, it's using generic to infer the type now, but I don't see how it can help with my use case

import { configureStore } from '@reduxjs/toolkit';
import type { PreloadedState } from '@reduxjs/toolkit';

type RootState = ReturnType<typeof store.getState>;

// actual store used in the app
const store = configureStore({
  reducer: persistedReducer,
  // ....
});

// for unit testing
const createStore = (preloadedState?: PreloadedState<RootState>) => configureStore({
  reducer: persistedReducer,
  preloadedState,
  // ......
});
@harry-gocity
Copy link

I found replacing it with Partial<RootState> kept typescript happy 🤷🏼‍♂️

@EskiMojo14
Copy link
Collaborator

EskiMojo14 commented Dec 5, 2023

the complete answer is Parameters<typeof reducer>[0], because preloadedState is anything that's valid to be the first parameter for your reducer.

if you've used combineReducers (or let configureStore do it for you by passing a map object to reducer) then that'll usually be Partial<RootState> (this doesn't take into account if you have any nested combineReducers calls, for example).

One thing to note is that it looks like you're using Redux Persist, which hasn't been updated to handle v5's PreloadedState generic (and honestly may never - it looks like it's rarely maintained). You can fix this yourself, by adding an overload via module augmentation:

import type { Action, Reducer } from "redux";
import type { PersistConfig, PersistState } from "redux-persist";

declare module "redux-persist" {
  export function persistReducer<S, A extends Action = Action, P = S>(
    config: PersistConfig<S>,
    baseReducer: Reducer<S, A, P>,
  ): Reducer<
    S & { _persist: PersistState },
    A,
    P & { _persist?: PersistState }
  >;
}

Here's a playground so you can see the difference.

@tmkn
Copy link

tmkn commented Dec 5, 2023

Also having the same problem, using only redux-toolkit.
Replacing PreloadedState<RootState> with Partial<RootState> works but not sure it is the best approach.

@EskiMojo14
Copy link
Collaborator

You can also use a type helper to extract the preloaded state from a given reducer:

import type { Reducer } from "redux";

type PreloadedStateFromReducer<R extends Reducer<any, any, any>> = R extends Reducer<any, any, infer P> ? P : never

type PreloadedState = PreloadedStateFromReducer<typeof reducer>

Generally though it's entirely valid to just do Parameters<typeof reducer>[0] or Partial<RootState>.

@alexandrsashin
Copy link

alexandrsashin commented Dec 5, 2023

In https://redux.js.org/usage/migrations/migrating-rtk-2#preloadedstate-type-removed-in-favour-of-reducer-generic authors write

type Reducer<S, A extends Action, PreloadedState = S> = (
  state: S | PreloadedState | undefined,
  action: A
) => S

<...>

For most normal reducers, S | undefined accurately describes what can be passed in for the preloadedState. However the combineReducers function allows for a preloaded state of Partial<S> | undefined.

I understood it in such a way:

const createStore = (preloadedState?: PreloadedState<RootState>) => ...
  1. If you have only one reducer, use RootState instead of PreloadState<RootState>.
  2. If you have multiple reducers, use Partial<RootState> instead of PreloadState<RootState>.

@markerikson do you agree with it?

@EskiMojo14
Copy link
Collaborator

essentially, yes - if your root reducer was created with combineReducers, it will accept a partial, whereas a normal reducer would not.

in the rare case of nested combineReducers calls, you could technically allow a partial there too:

const rootReducer = combineReducers({ foo, bar: combineReducers({ baz, qux }) });

type PreloadedState = Partial<{ foo: FooState; bar: Partial<{ baz: BazState; qux: QuxState }> }>

The type helper above would assist with getting the correct type.

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

6 participants