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

fix(action): Stop passing a function to store.dipatch and stop effect… #1914

Conversation

SerkanSipahi
Copy link
Contributor

… that is returns a function

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[x] Bugfix
[ ] 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?

Closes #1906

What is the new behavior?

Does this PR introduce a breaking change?

[ ] Yes
[x] No

@@ -13,12 +25,7 @@ export class ActionsSubject extends BehaviorSubject<Action>
}

next(action: Action): void {
if (typeof action === 'undefined') {
Copy link
Contributor Author

@SerkanSipahi SerkanSipahi Jun 4, 2019

Choose a reason for hiding this comment

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

There were no tests for this block (for the errors in the expression sections). Should I write some for it?

@ngrxbot
Copy link
Collaborator

ngrxbot commented Jun 4, 2019

Preview docs changes for 216a4a5 at https://previews.ngrx.io/pr1914-216a4a5/

@@ -17,6 +19,7 @@ export class EffectsRunner implements OnDestroy {
if (!this.effectsSubscription) {
this.effectsSubscription = this.effectSources
.toActions()
.pipe(tap(action => throwErrorOnInvalidAction(action)))
Copy link
Member

Choose a reason for hiding this comment

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

We don't need to to verify the action here. The action returned will be dispatched to the store, and will be verified there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are absolutely right. The circle closes at the place!
EffectsRunner.start() => Store.next() => ActionsSubject.next() :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its removed!

throw new TypeError(`Actions must be objects`);
} else if (typeof action === 'function') {
throw new TypeError(`
Actions as function are not now allowed.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Actions as function are not now allowed.
Actions as function are not allowed.

} else if (typeof action === 'function') {
throw new TypeError(`
Actions as function are not now allowed.
Make sure your action is called e.g someAction()`);
Copy link
Member

Choose a reason for hiding this comment

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

Should we make this more explicit?

Dispatch expected an object, instead it received xxx
If you're using the createAction function, make sure to invoke the function before dispatching the action. E.g. someAction should be someAction().

Copy link
Contributor Author

@SerkanSipahi SerkanSipahi Jun 5, 2019

Choose a reason for hiding this comment

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

What about this?

const typeOf = (value: any) => ({}).toString.call(value).slice(8, -1).toLowerCase();

...code...
...code...
...code...

next(action: Action): void {
  if(typeOf(action) !== 'object') {
    throw new TypeError(`
      Dispatch expected an object, instead it received ${action}.
      If you're using the createAction function, make sure to invoke the function
      before dispatching the action. E.g. someAction should be someAction().`);    
  } else if (typeof action.type === 'undefined') {
    throw new TypeError(`Actions must have a type property`);
  }
  super.next(action);
}

Should we test this cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its more explicit now (commited)

@ngrxbot
Copy link
Collaborator

ngrxbot commented Jun 5, 2019

Preview docs changes for 55ed464 at https://previews.ngrx.io/pr1914-55ed464/

@brandonroberts
Copy link
Member

@SerkanSipahi
Copy link
Contributor Author

Is there any place in ngrx where we can put helper methods? e.g. this:

const throwErrorOnInvalidAction = action => {
  if(typeOf(action) !== 'object') {
    throw new TypeError(`
      Dispatch expected an object, instead it received ${action}.
      If you're using the createAction function, make sure to invoke the function
      before dispatching the action. E.g. someAction should be someAction().`);    
  } else if (typeof action.type === 'undefined') {
    throw new TypeError(`Actions must have a type property`);
  }
}

This can be used can also in https://github.com/ngrx/platform/blob/master/modules/effects/src/effect_notification.ts#L33

@brandonroberts
Copy link
Member

No. In this case they will just have to be separate. I don't think the function needs to exist in the public API for either library.

@SerkanSipahi
Copy link
Contributor Author

SerkanSipahi commented Jun 5, 2019

@brandonroberts I hope I understood you correctly! Like this?

const typeOf = action =>  ({}).toString.call(action).slice(8, -1).toLowerCase();

function isAction(action: any): action is Action {
  return typeOf(action) === 'object' && action && action.type && typeof action.type === 'string';
}

@ngrxbot
Copy link
Collaborator

ngrxbot commented Jun 5, 2019

Preview docs changes for 2b25c95 at https://previews.ngrx.io/pr1914-2b25c95/

@SerkanSipahi
Copy link
Contributor Author

@brandonroberts its separated now (commited)

@@ -13,12 +19,14 @@ export class ActionsSubject extends BehaviorSubject<Action>
}

next(action: Action): void {
if (typeof action === 'undefined') {
throw new TypeError(`Actions must be objects`);
if (typeOf(action) !== 'object') {
Copy link
Member

Choose a reason for hiding this comment

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

would typeof action !== 'object' not work?

Copy link
Contributor Author

@SerkanSipahi SerkanSipahi Jun 6, 2019

Choose a reason for hiding this comment

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

There is a edge case when you pass accidently an array instead of object.

typeof [] // object

// this code snipped can recognize each kind of type

({}).toString.call([]).slice(8, -1).toLowerCase() // array

I just wanted a strict treatment regardless of whether we use typescript or not.

Copy link
Member

Choose a reason for hiding this comment

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

It's true that array is type of object as well, however:

  • we are trying to catch the case where function is passed instead of object, for example action creator is passed without calling it
  • even if array doesn't run into this check, it will be caught with the next if-statement typeof action.type === 'undefined'

Also, I find the code snippet ({}).toString.call([]).slice(8, -1).toLowerCase() to be somewhat "crafty" 😁and not very straightforward/readably. I'd rather avoid it.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @alex-okrushko.
To keep it readable I'm in favor of using typeof.

Plus we already have a compile error if we try to dispatch an array. The createAction is only the problem here.

image

Copy link
Contributor Author

@SerkanSipahi SerkanSipahi Jun 6, 2019

Choose a reason for hiding this comment

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

I had the same thoughts (compile error) after reading what @alex-okrushko wrote. I was to strict! I have to find a balance between typescript and javascript :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

({}).toString.call([]).slice(8, -1).toLowerCase() its removed. There is now a simple the for function.

@@ -30,8 +30,19 @@ export function reportInvalidActions(
}
}

const typeOf = (value: any) =>
Copy link
Member

Choose a reason for hiding this comment

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

Generally these should be structured as function declarations, e.g.

function typeOf(value: any) {...}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The typeOf is removed. We use typeof xxx as you suggested.

@ngrxbot
Copy link
Collaborator

ngrxbot commented Jun 7, 2019

Preview docs changes for c72dbde at https://previews.ngrx.io/pr1914-c72dbde/

@SerkanSipahi
Copy link
Contributor Author

SerkanSipahi commented Jun 7, 2019

@timdeschryver @alex-okrushko all code parts were adjusted according your code review suggestions.

@ngrxbot
Copy link
Collaborator

ngrxbot commented Jun 7, 2019

Preview docs changes for ca62d3a at https://previews.ngrx.io/pr1914-ca62d3a/

@ngrxbot
Copy link
Collaborator

ngrxbot commented Jun 7, 2019

Preview docs changes for dd9131e at https://previews.ngrx.io/pr1914-dd9131e/

@alex-okrushko
Copy link
Member

Looks good. Thanks Serkan!

throw new TypeError(`
Dispatch expected an object, instead it received function.
If you're using the createAction function, make sure to invoke the function
before dispatching the action. E.g. someAction should be someAction().`);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Everyone doesn't know what e.g. means.

Suggested change
before dispatching the action. E.g. someAction should be someAction().`);
before dispatching the action. For example, someAction should be someAction().`);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

if (typeof action === 'undefined') {
if (typeof action === 'function') {
throw new TypeError(`
Dispatch expected an object, instead it received function.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Dispatch expected an object, instead it received function.
Dispatch expected an object, instead it received a function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@ngrxbot
Copy link
Collaborator

ngrxbot commented Jun 9, 2019

Preview docs changes for 11b3e8a at https://previews.ngrx.io/pr1914-11b3e8a/

@ngrxbot
Copy link
Collaborator

ngrxbot commented Jun 9, 2019

Preview docs changes for 9619b37 at https://previews.ngrx.io/pr1914-9619b37/

@brandonroberts brandonroberts merged commit 78153cb into ngrx:master Jun 10, 2019
action &&
action.type &&
typeof action.type === 'string'
);
Copy link
Member

Choose a reason for hiding this comment

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

I think you don't need to wrap the return in brackets. This could be reverted to what it was before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, sorry, I didn't realize that! I had already wondered why prettier always jumped on when I committed something on NgRx. That doesn't happen with my private repos. I thought it was something like a hooks coming from ngrx repo.

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

Successfully merging this pull request may close these issues.

Strange behavior when passing action function into store.dispatch
5 participants