-
Notifications
You must be signed in to change notification settings - Fork 202
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
Adding a way for @dispatch methods to return a value that won't trigger a dispatch #497
Conversation
… a value that won't trigger a dispatch so they can conditionally dispatch based on any logic that suits the need
Hi, Thanks for the PR - I will give this some thought, although situations like this is making me question "was adding Feels like if something is decorated with eg: export class Component {
handleSubmit(form: any) {
if (formValid) {
this.doSubmit(form)
}
}
@dispatch()
doSubmit(form: any) {
return { type: 'FORM_SUBMITTED', payload: form };
}
}
// instead of
export class Component {
@dispatch()
doSubmit(form: any) {
if(formValid) {
return { type: 'FORM_SUBMITTED', payload: form };
}
else {
return { type: NgRedux.DISPATCH_NOOP }
}
}
} @danielfigueiredo @smithad15 @SethDavenport thoughts? |
Totally understand where you're coming from. I've asked myself the same question. Your example is great because it demonstrates the kind of scenario where I think this should be used: very simple quick checks. I found my self doing several similar little checks like those where I feel an additional function to handle is unnecessary and... boilerplatey? ..just more code that it needs be. One could even do it in the template but I think is nice and cleaner to do it this way e.g.
Ultimately, this gives the flexibility for people to use it however they choose. If they have strong philosophical opinions they could use it for super simple checks like the example or avoid it all together; if they choose to use it differently they have the option too. Unless you specifically want to angular-redux to be opinionated for this matter, the change is minimal and provides a nice convenience. Other tiny detail I was wondering was what the name of the constant should be. I ended up submitting DISPATCH_NOOP but NO_DISPATCH or others where high up in my list hehe Thanks for considering :) |
Small code prettification
Trying to find a middle ground here, I personally don't feel like having an action NOOP that NgRedux checks for in order to figure out whether to dispatch if (store && result) {
store.dispatch(result);
} But looking at reactjs/redux it's very clear that we can't fire null actions or anything alike: function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
...
} Thus, doing this make me feel like I'm going against redux. They intentionally fail any attempt to fire an invalid action (not a POJO, missing type selectTab(tab) {
if (this.active.id !== tab.id) {
this.ngRedux.dispatch(action);
}
} ^ this feels cleaner to me than dispatching noop in an |
I thought of returning false, undefined or null but I didn't like either of those, especially undefined or null introduces the possibility that the programmer just forgets to return and it sort of "fails" silently. An explicit value for doing this enforces you to either return an action or a known value for NgRedux to know you want to avoid dispatching all together. I don't feel this goes against redux. The whole point is for this to avoid a non-action or dummy one getting to redux (and middleware) in the first place. Exactly how do you think this goes against it; which redux principle does this go against? The idea is to add an additional convenience into the library that gives safe concise code through
I was also considering the possibility of |
You guys seem to agree that this shouldn't go into NgRedux so I've started thinking on what am I going to do to achieve this and thought I may just create my own |
Hey, late to the party, sorry. I've had very limited OSS time since I changed jobs :( That said my original intention with store was to have two 'interfaces': the "regular one" where you inject the "decalarative one" (aka the Decorator interface) for people like my who are obsessed with declarative components. This is largely what the decorators are for. The idea is that if you're doing simple stuff, the decorator interface is helpful, but if you're doing complicated stuff you always have the power of injecting I personally am fairly opinionated but I don't necessarily want to bake that into the API because who am I to tell people how to code? :). I don't want to limit good ideas that I haven't thought about. For your use case I'd be cool with allowing the decorated function to return null or undefined: probably can just do a That way we just need to update the API doc to spell out that @dispatch() does nothing to a falsy value and you can use it like this: @dispatch()
selectTab(tab) {
return this.active.id !== tab.id ? { type: .... } : null;
} |
If you're concerned that this results in hard-do-debug behaviour in the case where you forget a return statement then we could do a variant of this where we only skip the dispatch call if the function returns |
I agree with you on the returning I guess what I'm saying is that both you and I are at a high level proposing a similar solution: return a 'special' value - either In which case |
I'm happy with that (returning false and triple equal checking it). If everyone is happy, let me know if the change will make it through and I'll update the PR. |
I don't generally use the decorators due to testing and architectural patterns that I subscribe to, but returning |
@rart your example of @dispatch()
selectTab(tab) {
return (this.active.id !== tab.id)
? { type: .... }
: false
} Gives me a better sense of a use-case where your idea can help out. I'm more inline with the idea of not dispatching falsy values (null, undefined, false) than using a special |
src/decorators/dispatch.ts
Outdated
const store = getBaseStore(this) || NgRedux.instance; | ||
if (store) { | ||
store.dispatch(result); | ||
if (result !== NgRedux.DISPATCH_NOOP) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in the PR - I think having this be checking for undefined, or a falsy value instead of a special action-type/magic string would be a better fit.
Leaving it at the decorator level like this is appropriate and I like it (instead of trying to change the actual store.dispatch), as these decorators/helpers are more geared towards smoothing out use with Angular and going a step beyond providing a binding between Angular and Redux.
I recently updated master tweaking some of the style / formatting rules to use prettier - so you may need to rebase and run the formatter also.
Since this check is going into the If this was trying to change Also agree with @smithad15 in not wanting to have 'noop' action types flying around - creates noise in logs / etc, potential performance hits with reducers/selectors/going through middleware/etc - even if only minor, it's still noisy. I'm ok with moving forward with this, thanks for the contribution and feedback. |
Sorry, I didn't mean bad with going against Redux. My thinking was just that
type DispatcherFilter = <A extends Action, RootState>(action: A, store: Store<RootState>) => boolean;
const shouldDispatch: DispatcherFilter = (action: Action, store: Store<any>) => !!store;
export function dispatch(shouldDispatchAction: DispatcherFilter = shouldDispatch): PropertyDecorator {
return function decorate(
target: Object,
key: string | symbol | number,
descriptor?: PropertyDescriptor): PropertyDescriptor {
let originalMethod: Function;
const wrapped = function (this: any, ...args: any[]) {
const result = originalMethod.apply(this, args);
if (shouldDispatchAction(result, store)) {
store.dispatch(result);
}
return result;
} |
@rart been busy with the holidays, but will try and get this wrapped up in the next day or two. Sorry for the delays. |
* 'master' of https://github.com/angular-redux/store: Setup Prettier and pre-commit hook (angular-redux#499) v7.0.2 Clarify Versioning (angular-redux#498) # Conflicts: # src/decorators/dispatch.spec.ts * Changed conditional dispatched to be based on checking for false instead of special constant value.
src/decorators/dispatch.spec.ts
Outdated
@@ -108,7 +108,7 @@ describe('@dispatch', () => { | |||
).toHaveBeenCalledWith(expectedArgs); | |||
}); | |||
|
|||
it("shouldn't call dispatch", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOTE there seems to be a new hook that runs before you commit. I had a single quoted string like 'shouldn\'t call dispatch.'
and the hook was silently changing my single quotes to double quotes, making the ci tool that runs here on GitHub fail the tests. I got rid of the escaped single quote inside the string and then it all worked: beware of this behaviour!
Adjusted the As we discussed earlier in this thread, I used |
This is in v7.1.0 now which just got released, thanks! |
Sweet, thank you! |
Adding a way for methods that use the
@dispatch
decorator to return a value that won't trigger a dispatch so they can conditionally dispatch based on any logic that suits the need.Currently returning anything but an action would cause middleware like redux-observable to throw or returning a mock action like
{ type: 'noop' }
would still activate the root reducer that, even though technically nothing will happen, just seems nicer that you can avoid all together the store.dispatch from within the same @dispatch-decorated method.By having a specific value to allow this behaviour — rather than just using null or undefined — prevents accidental development errors of developers just forgetting to return.