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

[RTKQ] Type conflicts with TypedUseSelectorHook when using reselect 4.1.3 #1750

Closed
Grapedge opened this issue Nov 16, 2021 · 11 comments · Fixed by reduxjs/reselect#552
Closed
Labels
bug Something isn't working

Comments

@Grapedge
Copy link
Contributor

Problems

code sandbox

When using RTKQ, there are some type conflicts here:

// ...
export const store = configureStore({
  reducer: {
    test: testReducer,
    [pokemonApi.reducerPath]: pokemonApi.reducer
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(pokemonApi.middleware)
});

export type RootState = ReturnType<typeof store.getState>;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// ...
const test = useAppSelector(selectTest); // type error: Types of parameters 'state' and 'state' are incompatible.

Versions

  • react-redux: 7.2.6
  • reselect: 4.1.3
  • redux-toolkit: 1.6.2
@markerikson
Copy link
Collaborator

Can you show more details? What are the calculated state types here?

@joshuajung
Copy link

joshuajung commented Nov 16, 2021

I have been observing the same (I think) issue since a recent dependency upgrade. Wasn't able to track it down yet, but this is one of the full type errors:

Types of parameters 'state' and 'state' are incompatible.
    Type 'CombinedState<{ fooApi: CombinedState<...>; }>' is not assignable to type '{ readonly [$CombinedState]?: undefined; fooApi: { ...; }; }'.
      The types of 'fooApi.queries' are incompatible between these types.
        Type 'QueryState<ReplaceTagTypes<{ postApiAuthGetMethods: MutationDefinition<PostApiAuthGetMethodsApiArg, BaseQueryFn<any, unknown, unknown, {}, {}>, never, AuthGetMethodsResponseDto, "fooApi">; ... 38 more ...; patchApiTenantsOptOut: MutationDefinition<...>; }, "SurveyDetailsByAnswerToken" | ... 3 more ... | "OrderPositi...' is not assignable to type '{ [x: string]: { status: QueryStatus.uninitialized; originalArgs?: undefined; data?: undefined; error?: undefined; requestId?: undefined; endpointName?: string | undefined; startedTimeStamp?: undefined; fulfilledTimeStamp?: undefined; } | { ...; } | { ...; } | { ...; } | undefined; }'.
          'string' index signatures are incompatible.
            Type 'QuerySubState<unknown> | undefined' is not assignable to type '{ status: QueryStatus.uninitialized; originalArgs?: undefined; data?: undefined; error?: undefined; requestId?: undefined; endpointName?: string | undefined; startedTimeStamp?: undefined; fulfilledTimeStamp?: undefined; } | { ...; } | { ...; } | { ...; } | undefined'.
              Type '{ status: QueryStatus.fulfilled; error: undefined; originalArgs: unknown; requestId: string; endpointName: string; startedTimeStamp: number; data: unknown; fulfilledTimeStamp: number; }' is not assignable to type '{ status: QueryStatus.uninitialized; originalArgs?: undefined; data?: undefined; error?: undefined; requestId?: undefined; endpointName?: string | undefined; startedTimeStamp?: undefined; fulfilledTimeStamp?: undefined; } | { ...; } | { ...; } | { ...; } | undefined'.
                Type '{ status: QueryStatus.fulfilled; error: undefined; originalArgs: unknown; requestId: string; endpointName: string; startedTimeStamp: number; data: unknown; fulfilledTimeStamp: number; }' is not assignable to type '{ status: QueryStatus.rejected; data?: {} | undefined; fulfilledTimeStamp?: number | undefined; originalArgs: {}; requestId: string; endpointName: string; startedTimeStamp: number; error: { ...; }; }'.
                  Types of property 'status' are incompatible.
                    Type 'QueryStatus.fulfilled' is not assignable to type 'QueryStatus.rejected'.

@Grapedge
Copy link
Contributor Author

Grapedge commented Nov 16, 2021

Yes, but the original error is too long to display and read.

After researching for a while, I found a smaller code that can cause errors:

// from rtkq:
type QueryCacheKey = string & {
  _type: 'queryCacheKey'
}

// from reselect
type Computed =  /** from reselect */ ComputeDeep<QueryCacheKey>

type Extends = Computed extends QueryCacheKey ? true : false // => false

And this is the result of Computed:

type Computed = {
    [x: number]: string;
    toString: (() => string) & (() => string);
    charAt: (pos: number) => string;
    charCodeAt: (index: number) => number;
    concat: (...strings: string[]) => string;
    indexOf: (searchString: string, position?: number | undefined) => number;
    ... 44 more ...;
    _type: 'queryCacheKey';
}

@phryneas
Copy link
Member

QueryCacheKey is an internal type you should never really be using though.

@Grapedge
Copy link
Contributor Author

Grapedge commented Nov 16, 2021

QueryCacheKey is one of the reasons that cause this problem. The state of the api is also included in RootState, so the RootState looks like:

type RootState = ReturnType<typeof store.getState>;
// => the result of it:
{
  test: { ... },
  // ...
  api: {
    queries: { ... },
    mutations: { ... },
    provided: { ... },
    subscriptions: { ... }
  }
}

and api.provided uses QueryCacheKey:

// provided: InvalidationState<E>;
type InvalidationState<TagTypes extends string> = {
  [_ in TagTypes]: {
      [id: string]: Array<QueryCacheKey>;
  };
};

Then, in reselect 4.1.3, MergeParameters, used to get state from selectors, try to expand our state recursively to make it easy to read. In other words, if the step 10 is deleted, the problem seems to be gone?

/** Recursively expand all fields in an object for easier reading */
export type ExpandItems<T extends readonly unknown[]> = {
  [index in keyof T]: T[index] extends T[number] ? ComputeDeep<T[index]> : never
}

And in ComputeDeep:

// line 385
    : { [K in keyof A]: ComputeDeep<A[K], A | Seen> } & unknown

It may not handle this form of string&{ _type: 'queryCacheKey' } well.

These comprehensive reasons lead to the problem. It may not be a good description, I hope I made it clear(+﹏+)~

@phryneas
Copy link
Member

Hmm, QueryCacheKey should not be leaking out, so I'll have to take another look at that in the future - but at the same time, this also looks like an error in reselect.

@phryneas phryneas added the bug Something isn't working label Nov 16, 2021
@markerikson
Copy link
Collaborator

Yeah, and that might be related to another Reselect 4.1.3 TS error someone reported today.

I'll try adding these as additional typetest cases this evening and work to fix them.

@markerikson
Copy link
Collaborator

Related: reduxjs/reselect#550

@markerikson
Copy link
Collaborator

Well, the "good" news is that I can reproduce this, and useAppSelector isn't even needed (which makes sense).

Investigating further...

@markerikson
Copy link
Collaborator

markerikson commented Nov 17, 2021

Yeah, confirmed that removing the ExpandItems wrapper lets that code compile clean, as does the rest of the file.

I'm going to remove that line. It was an interesting idea but I don't think it's sufficiently useful.

@Grapedge appreciate the detailed analysis and breakdown!

@markerikson
Copy link
Collaborator

Should be fixed in https://github.com/reduxjs/reselect/releases/tag/v4.1.4 .

(Also: whoa! how did merging that PR close this issue, especially across repos? I guess I did say "fixes" + a link to this issue, sooooo.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants