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

feat(store): use variadic tuple types for createSelector #3023

Merged

Conversation

david-shortman
Copy link
Contributor

@david-shortman david-shortman commented May 18, 2021

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[ ] Bugfix
[x] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] Other... Please describe:

What is the current behavior?

Types were manually created for set numbers of arguments.

Completes #2715 (I don't see any reducer code that still needs variadic typing?)

What is the new behavior?

createSelector stays strongly typed for an infinite number of arguments.

Does this PR introduce a breaking change?

[x] Yes
[ ] No

Some small typing adjustments.

Comment on lines +276 to +281
/**
* @deprecated Selectors with props are deprecated, for more info see {@link https://github.com/ngrx/platform/issues/2980 Github Issue}
*/
export function createSelector<State, Props, S1, Result>(
Copy link
Contributor Author

@david-shortman david-shortman May 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left the existing definitions for createSelector when creating selectors with props since it's deprecated and will eventually be removed

@ngrxbot
Copy link
Collaborator

ngrxbot commented May 18, 2021

Preview docs changes for 41df527 at https://previews.ngrx.io/pr3023-41df527a/

@david-shortman david-shortman marked this pull request as ready for review May 20, 2021 02:22
@david-shortman
Copy link
Contributor Author

david-shortman commented May 20, 2021

The example-app tests are failing on circleci and yet "it works on my machine"

Screen Shot 2021-05-19 at 22 18 52

the error complains about selectAll being the wrong type MemoizedSelectorWithProps and yet:

Screen Shot 2021-05-19 at 22 18 06

 FAIL   Example App  projects/example-app/src/app/books/reducers/books.reducer.spec.ts
  ● Test suite failed to run

    modules/entity/src/state_selectors.ts:27:9 - error TS2322: Type 'MemoizedSelectorWithProps<any, unknown, any, DefaultProjectorFn<any>>' is not assignable to type '(state: any) => T[]'.

    27         selectAll,
               ~~~~~~~~~

      modules/entity/src/models.ts:83:3
        83   selectAll: (state: V) => T[];
             ~~~~~~~~~
        The expected type comes from property 'selectAll' which is declared here on type 'EntitySelectors<T, any>'
    modules/entity/src/state_selectors.ts:35:7 - error TS2322: Type 'MemoizedSelectorWithProps<any, unknown, any, DefaultProjectorFn<any>>' is not assignable to type '(state: any) => T[]'.

    35       selectAll: createSelector(selectState, selectAll),
             ~~~~~~~~~

@timdeschryver
Copy link
Member

Does that error disappears if you remove the deprecated selector with props?
The error message in the CI, indicates that that type of selector is used.

@david-shortman david-shortman force-pushed the create-selector-type-update branch from 0dc1ca4 to c53a8d4 Compare June 4, 2021 01:19
@david-shortman
Copy link
Contributor Author

Since there can be minor type adjustments that are breaking, it may be best to pair this with a pr that completely removes SelectorWithProps to finish off dealing with selectors for v13: #3035.

@david-shortman
Copy link
Contributor Author

So it all works now, but in some circumstances (as evidenced by the few changes to other files) the overloads are not properly recognized if the projector functions have explicitly typed parameters. Not sure why and if anyone has advanced TS knowledge to avoid this as to not make this a breaking change, that'd be litty.

@brandonroberts
Copy link
Member

Is this dependent on selectors with props being removed? If so, I say we close it until then.

@david-shortman
Copy link
Contributor Author

Is this dependent on selectors with props being removed? If so, I say we close it until then.

This is not dependent on selectors with props being removed.

@timdeschryver
Copy link
Member

Since the types in some tests are changed, I think this will lead to some breaking changes?
If that's the case, this PR should also have to wait for v13.

@david-shortman
Copy link
Contributor Author

Since the types in some tests are changed, I think this will lead to some breaking changes?
If that's the case, this PR should also have to wait for v13.

Right now it's a breaking change, but I'm pretty sure there's something that could be done either with the types or with a Typescript issue that would cause the overloads to not be improperly interpreted... if someone smarter than me could find a solution 😅.

@david-shortman david-shortman force-pushed the create-selector-type-update branch 2 times, most recently from 08b00f3 to 3265126 Compare July 10, 2021 03:09
@david-shortman
Copy link
Contributor Author

@timdeschryver This is a breaking change because you cannot provide a specific set of generic type arguments to createSelector anymore.

So createSelector<State, S1, S2, S3, Result>(...) is invalid now, and instead, if someone were to use explicit generic type arguments, they would have to do this: `createSelector<State, [S1, S2, S3], Result>.

@@ -1,7 +1,7 @@
import { createSelector } from '@ngrx/store';
import { Stories, Story } from './story';

export const selectStories = createSelector<Story[], Story[], Story[][]>(
export const selectStories = createSelector<Story[], [Story[]], Story[][]>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly provided generic arguments must take the format <State, [S1, S2, Etc], Result>

@MaximSagan
Copy link

Since the types in some tests are changed, I think this will lead to some breaking changes? If that's the case, this PR should also have to wait for v13.

@timdeschryver Seems like v13 of Angular is approaching, and it seems since the last NGRX v12 release you've started deprecating features in master, so I guess we're gearing up for NGRX v13 too?
Could we get this PR merged into master now?

@timdeschryver
Copy link
Member

@MaximSagan we're not there yet.
We will try to follow the Angular releases, so we can have v13 released shortly hereafter.

@david-shortman
Copy link
Contributor Author

@MaximSagan we're not there yet.
We will try to follow the Angular releases, so we can have v13 released shortly hereafter.

@timdeschryver Is this planned for v13? I noticed it wasn't included for the first beta.

@david-shortman
Copy link
Contributor Author

@MaximSagan we're not there yet.
We will try to follow the Angular releases, so we can have v13 released shortly hereafter.

@brandonroberts Wondering if there is a blocker for this being included in v13?

@brandonroberts
Copy link
Member

@david-shortman no blocker. I wasn't sure if this was ready to be merged yet

@david-shortman
Copy link
Contributor Author

david-shortman commented Nov 9, 2021

@david-shortman no blocker. I wasn't sure if this was ready to be merged yet

@brandonroberts It should be. I'm not sure why it doesn't have any reviews yet, but it is ready.

@timdeschryver
Copy link
Member

Sorry for my late response, I can take a look at it later this week and provide some feedback then.

@david-shortman
Copy link
Contributor Author

Sorry for my late response, I can take a look at it later this week and provide some feedback then.

@timdeschryver thanks! Also, I've created a PR dependent on this one, #3223, which offers a gentle path through depreciation to move towards a strongly-typed projector method on selectors.

Copy link
Member

@timdeschryver timdeschryver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏👏 @david-shortman

This looks good to me.
We could also provide a schematic that provides a fix for the breaking change, e.g.

BEFORE:

createSelector<Story[], Story[], Story[][]>

AFTER:

//        needs to be a tuple 👇
createSelector<Story[], [ Story[] ] , Story[][]>

@david-shortman
Copy link
Contributor Author

👏👏 @david-shortman

This looks good to me.

We could also provide a schematic that provides a fix for the breaking change, e.g.

BEFORE:

createSelector<Story[], Story[], Story[][]>

AFTER:

//        needs to be a tuple 👇

createSelector<Story[], [ Story[] ] , Story[][]>

I haven't written a schematic before, but it could be fun to try.

Should I go for that in a separate PR?

@brandonroberts
Copy link
Member

@david-shortman @timdeschryver is the tuple only required when you strictly define all the types with createSelector?

@david-shortman
Copy link
Contributor Author

david-shortman commented Nov 10, 2021

@david-shortman @timdeschryver is the tuple only required when you strictly define all the types with createSelector?

The generic arguments are inferred, so that you can use const inferred = createSelector(() => 1, () => 2, (one, two) => 3).

When manually specifying the generic arguments, you have to specify the selector's list of selector return values, like const strictlySpecified = createSelector<State, [1, 2], 3>(...)

Copy link
Member

@markostanimirovic markostanimirovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work David! 🥇

Btw, it would be great to add type tests for selectors :)

export function createSelector<State, S1, Result>(
s1: Selector<State, S1>,
projector: (s1: S1) => Result
export function createSelector<State, S extends unknown[], Result>(
Copy link
Member

@markostanimirovic markostanimirovic Nov 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to give a meaningful name to the S generic because it's not clear at first what is its purpose. The name could be: Slices

s1: Selector<State, S1>,
projector: (s1: S1) => Result
export function createSelector<State, S extends unknown[], Result>(
...args: [...Selector<State, unknown>[], unknown] &
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice hack 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙇‍♂️

props: Props
) => Result
): MemoizedSelectorWithProps<State, Props, Result>;
export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, Result>(

export function createSelector<State, S extends unknown[], Result>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same here: S => Slices

@david-shortman
Copy link
Contributor Author

david-shortman commented Nov 11, 2021

Great work David! 🥇

Btw, it would be great to add type tests for selectors :)

I think ts-snippet can only validate the return type of the function, not whether the function inferred the parameters correctly.

In #3223, the return type is validated in type checks since the Slices are used in MemoziedSelector.

Copy link
Member

@markostanimirovic markostanimirovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@markostanimirovic markostanimirovic changed the title refactor(store) used variadic tuple types for createSelector feat(store): use variadic tuple types for createSelector Nov 11, 2021
@markostanimirovic
Copy link
Member

Changed PR type from refactor to feature, for two reasons:

  • Breaking change with types
  • The createSelector function can now accept any number of input selectors.

@brandonroberts brandonroberts merged commit 367d9b4 into ngrx:master Nov 11, 2021
@brandonroberts
Copy link
Member

🥳

@david-shortman david-shortman deleted the create-selector-type-update branch November 11, 2021 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants