Skip to content

select overload not match when enable strict mode in tsconfig #2780

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

Closed
hanjeahwan opened this issue Nov 11, 2020 · 19 comments · Fixed by #3063
Closed

select overload not match when enable strict mode in tsconfig #2780

hanjeahwan opened this issue Nov 11, 2020 · 19 comments · Fixed by #3063

Comments

@hanjeahwan
Copy link

I create a angular project with strict flag and try add in a feature store. But when I try to select the state with selector have created it appear overload not match. When I turn off strictFunctionTypes everything work well.

Minimal reproduction of the bug/regression with instructions:

Repo URL

Expected behavior:

No error

Versions of NgRx, Angular, Node, affected browser(s) and operating system(s):

NgRx : 10.0.1
Angular: 10.1.4

@timdeschryver
Copy link
Member

Could this be because the store is types as object?
I was able to solve this issue by typing the store as any, but there's probably a better solution...

- export class Store<T = object> extends Observable<T>
+ export class Store<T = any> extends Observable<T>

@hanjeahwan you can solve the issue by typing the store in the component.

-  constructor(private store: Store) {}
+  constructor(private store: Store<any>) {}
// or
+  constructor(private store: Store<GameState>) {}

@timdeschryver
Copy link
Member

On the NgRx Discord Server it was mentioned that typing the feature selector as follows should also work:

- export const selectFeature = createFeatureSelector<AppState, GameState>(
-  scoreboardFeatureKey
- );

+ export const selectFeature = createFeatureSelector<GameState>(
+  scoreboardFeatureKey
+ );

@hanjeahwan
Copy link
Author

@timdeschryver thanks your help 😁

@pacoita
Copy link

pacoita commented Feb 5, 2021

I just leave my experience as well, as I had a similar issue, solved thanks to Tim's suggestion.
I am working with a feature state in a NX Workspace (using strict mode).

NX command to create a feature ngrx state generates automatically a "PartialState" that should be used explicit as Store type to solve the strictFunctionTypes check error.

// selectors
export const getPlacesState = createFeatureSelector<PlacesPartialState, PlaceState>(placesFeatureKey);
export const getAllPlaces = createSelector(getPlacesState, (state: PlaceState) => selectAll(state));
// reducers
export const placesFeatureKey = 'places';

export interface PlacesPartialState {
  readonly [placesFeatureKey]: PlaceState;
}
// Angular component
constructor(private store: Store<PlacesPartialState>) {}  <-- I had to use PlacesPartialState as type
const places$ = this.store.select(getMyPlaces);

@pacoita
Copy link

pacoita commented Feb 10, 2021

I updated to the latest release (1.16.0), and the ES-Lint Message is changed according to the last PR.
However, if I remove the generic type as suggested I get an error due to the type mismatch (when in strict mode)

Screenshot 2021-02-10 at 17 47 13

How this case should be handled if the Store should not have a generic type anymore?

@david-shortman
Copy link
Contributor

david-shortman commented Jul 2, 2021

I feel like I'm missing something @pacoita.

If you strongly type the feature that's selected by providing the entire required global state to createFeatureSelector<TheGlobalState, TheFeatureState>, then the subsequently produced selectors must be used with a Store<TheGlobalState>.

There's no need to declare the global state shape anymore to createFeatureSelector . Instead, just pass your feature's state, like createFeatureSelector <TheFeatureState>, and then you can use it with any injected Store.

@david-shortman
Copy link
Contributor

The docs's selector page (https://ngrx.io/guide/store/selectors) only shows usage of createFeatureSelector with the entire app state, however. That should be updated.

@david-shortman
Copy link
Contributor

I've added a PR for a clarification of this behavior in the docs: https://github.com/ngrx/platform/pull/3063/files

@MartinMa
Copy link

I'm sorry I have to comment on this closed issue. But the problem persists for me.

I'm using Angular 12.2.5 and NgRx (Store) 12.4.0.

My component constructor currently has the following argument: private readonly store: Store<AppState> and eslint gives me the warning eslint(ngrx/no-typed-global-store).

If I remove the type <AppState> from the argument, I later get errors like this using selectors.

No overload matches this call.
  Overload 1 of 9, '(mapFn: (state: object) => boolean): Observable<boolean>', gave the following error.
    Argument of type 'MemoizedSelector<AppState, boolean, DefaultProjectorFn<boolean>>' is not assignable to parameter of type '(state: object) => boolean'.
      Types of parameters 'state' and 'state' are incompatible.
        Type '{}' is missing the following properties from type 'AppState': resource, menu
  Overload 2 of 9, '(key: never): Observable<never>', gave the following error.
    Argument of type 'MemoizedSelector<AppState, boolean, DefaultProjectorFn<boolean>>' is not assignable to parameter of type 'never'.ts(2769)

So essentially eslint is complaining that object doesn't fit my AppState type. From this discussion I could learn that this is due to the fact that Angular applies the strict TypeScript compiler option by default.

My question: What is the recommended solution here? I can disable the rule, but there seems to be a problem with this rule. If you would type the store to any by default (instead of object) I would not get a warning. But that is out of the scope of what I can do in my code.

@MartinMa
Copy link

I'm making this follow-up comment, because I sort of made it work and then ran into the next problem.

Nevertheless, I think this will help others, that run into the same problem. The following code is an example how I used to define my selectors. But this way I get compiler errors like the one mentioned earlier.

export const selectFeature = (state: AppState) => state.feature;
 
export const selectFeatureCount = createSelector(
  selectFeature,
  (state: FeatureState) => state.counter
);

This is the first thing you read in the documentation about selectors on https://ngrx.io/guide/store/selectors. But it gives you errors when used in conjunction with Angular and the default strict compiler mode.

But it can be fixed by changing the first line to use createFeatureSelector like this:

export const selectFeature = createFeatureSelector<FeatureState>('feature');

export const selectFeatureCount = createSelector(
  selectFeature,
  (state: FeatureState) => state.counter
);

This seems like a small change, but it took multiple hours to figure it out and it would be helpful if it was mentioned in the docs, because it is essential for Angular strict mode users (which is the default for new users).

Worth mentioning, this leads to new problems.

Since selectors with props are deprecated, it is recommended to use factory selectors instead. So I did, but strict mode forces me to define all the types, including return types. I didn't find any example on how to make a factory selector in the docs. But I did find examples in some of the issues here on GitHub. The examples are usually missing return types. So this is what I came up with.

export const selectFoobar = (arg: number): MemoizedSelector<object, number> => {
    return createSelector(selectFeature , (state: FeatureState) => {
        return state.counter + arg;
    });
};

The problem lies in MemoizedSelector<object, number>. ESLint complains Don't use object as a type. The object type is currently hard to use. When I use FeatureState instead of object I get the same errors as mentioned in the previous post. It is difficult. What should I do now?

@david-shortman
Copy link
Contributor

@MartinMa Try using createFeature to create your base feature selectors instead.

I do think the lint settings you're using are a bit harsh. Having to explicitly declare the types of everything is a real pain when type inference is so useful.

// in reducer
const feature = createFeature({
  name: 'feature',
  reducer: createReducer(initialState),
});

// selector produced for free
feature.selectCount;

// in selectors
export const selectFoobar = (arg: number): MemoizedSelector<Record<string, unknown>, FeatureState> => {
    return createSelector(feature.selectCount, (counter: number) => {
        return counter + arg;
    });
};

@Yohandah
Copy link

Yohandah commented Feb 7, 2022

@timdeschryver I'm so sorry to bring this closed issue as well but how am I supposed to make this work; I'm confused after all the threads I've read at eslint-plugin-ngrx and here.

I have the following

login.selector.ts

...
export const selectLoginState: MemoizedSelector<LoginState, LoginState> =
  createFeatureSelector<LoginState>(loginFeatureKey);

export const selectIsAuthenticating: MemoizedSelector<LoginState, boolean> = createSelector(
  selectLoginState,
  (state: LoginState) => state.isAuthenticating,
);
...

login.component.ts

constructor(private store: Store) { }
...
this.authenticating$ = this.store.select(selectIsAuthenticating)

And I have the following error :

TS2769: No overload matches this call.   Overload 1 of 9, '(mapFn: (state: object) => boolean): Observable', gave the following error.     Argument of type 'MemoizedSelector<LoginState, boolean, DefaultProjectorFn>' is not assignable to parameter of type '(state: object) => boolean'.       Types of parameters 'state' and 'state' are incompatible.         Type '{}' is missing the following properties from type 'LoginState': isAuthenticating, token, isGardianRedirecting   Overload 2 of 9, '(key: never): Observable', gave the following error.     Argument of type 'MemoizedSelector<LoginState, boolean, DefaultProjectorFn>' is not assignable to parameter of type 'never'

If I type my store object I have the following

ESLint: Store should not be typed, use Store (without generic) instead.(ngrx/no-typed-global-store

@timdeschryver
Copy link
Member

@Yohandah is there a reason why everything is explicitly typed?
And have you tried to remove those types?

export const selectLoginState = createFeatureSelector<LoginState>(loginFeatureKey);

export const selectIsAuthenticating = createSelector(
  selectLoginState,
  (state) => state.isAuthenticating,
);

@Yohandah
Copy link

Yohandah commented Feb 7, 2022

@timdeschryver
My project has an ESLint rule that forces explicit typing for everything in the project and forbid any for consistency.

I have tried to remove the types after I saw comments here and there, but my IDE was still showing the error unfortunately. I will try again tomorrow and then restart WebStorm maybe? If it works I will tweak my ESLint to deactivate this rule in *.selector.ts files

@david-shortman
Copy link
Contributor

I'm making this follow-up comment, because I sort of made it work and then ran into the next problem.

Nevertheless, I think this will help others, that run into the same problem. The following code is an example how I used to define my selectors. But this way I get compiler errors like the one mentioned earlier.

export const selectFeature = (state: AppState) => state.feature;

 

export const selectFeatureCount = createSelector(

  selectFeature,

  (state: FeatureState) => state.counter

);

This is the first thing you read in the documentation about selectors on https://ngrx.io/guide/store/selectors. But it gives you errors when used in conjunction with Angular and the default strict compiler mode.

But it can be fixed by changing the first line to use createFeatureSelector like this:

export const selectFeature = createFeatureSelector<FeatureState>('feature');



export const selectFeatureCount = createSelector(

  selectFeature,

  (state: FeatureState) => state.counter

);

This seems like a small change, but it took multiple hours to figure it out and it would be helpful if it was mentioned in the docs, because it is essential for Angular strict mode users (which is the default for new users).

Worth mentioning, this leads to new problems.

Since selectors with props are deprecated, it is recommended to use factory selectors instead. So I did, but strict mode forces me to define all the types, including return types. I didn't find any example on how to make a factory selector in the docs. But I did find examples in some of the issues here on GitHub. The examples are usually missing return types. So this is what I came up with.

export const selectFoobar = (arg: number): MemoizedSelector<object, number> => {

    return createSelector(selectFeature , (state: FeatureState) => {

        return state.counter + arg;

    });

};

The problem lies in MemoizedSelector<object, number>. ESLint complains Don't use object as a type. The object type is currently hard to use. When I use FeatureState instead of object I get the same errors as mentioned in the previous post. It is difficult. What should I do now?

For some reason, with the new function signature for createSelector, explicit type arguments cause Typescript to think the selector is a SelectorWithProps.
We'll need to resolve either by #3268 (comment) or a solution to the TS error causing improper inference (issue also referenced in link).

@Yohandah
Copy link

Yohandah commented Feb 8, 2022

@timdeschryver This is not working when I remove the type :
Sans titre

This is my tsconfig with strict mode :

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2017",
    "module": "es2020",
    "lib": ["es2018", "dom"],
    "paths": {
      "@core/*": ["src/app/core/*"]
    }
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

@markostanimirovic
Copy link
Member

@timdeschryver This is not working when I remove the type : Sans titre

This is my tsconfig with strict mode :

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2017",
    "module": "es2020",
    "lib": ["es2018", "dom"],
    "paths": {
      "@core/*": ["src/app/core/*"]
    }
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

@Yohandah

The first generic argument for MemoizedSelector should not be the feature state type: LoginState.
Instead, it should be the type of your global state. Use object if you inject Store without generic argument (global state type) in components/effects:

// selector
export const selectIsAuthenticating: **MemoizedSelector<object, boolean>** = createSelector(
  selectLoginState,
  (state) => state.isAuthenticating,
);

// component
isAuthenticating$ = this.store.select(selectIsAuthenticating);

constructor(private readonly store: Store) {}

@timdeschryver
Copy link
Member

@Yohandah from the screenshot, I can see that the types are still there.
If you could create a small reproduction, we can help figure out what's going on, and if needed create a new ticket to track the bug.

@Yohandah
Copy link

Yohandah commented Feb 8, 2022

@timdeschryver @markostanimirovic Nice it's working after typing them with object then completely removed the types and disabled @typescript-eslint/typedef rule for .selector.ts files ! Thanks a lot !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
7 participants