Handling form state with NGRX / #Effects - javascript

I have a form and on submit I dispatch an action which is caught by an effect. The effect then does the http call. I'm wondering how off the back of this action completing / failing I would do the following:
show a success message once the action completes
reset all the fields ready for when the form is next used
show an error off the back of the action failing
I understand I could re-dispatch an action to populate the store with several flags success, error etc. However, resetting the form will probably be done by calling a function. Would it be acceptable to subscribe to the store and calling the relevant reset function in the child? It's almost as if I'd like components to be able to listen to actions just like effects can.

If your effect-rest does not affect the store, but only should display a notification - then there are two ways I'd say:
1) Inject Actions in your component as you suggested yourself:
class SomeComponent {
constructor(actions: Actions) {
actions.ofType(SOME_EVENT)
.do(myThing)
.takeUntil(componentIsDestroyed)
.subscribe();
}
}
2) Or don't go the effects- and actions-way at all and just call a simple service-method:
class SomeComponent {
constructor(myService: CoolService) {
}
onClick(): void {
mySevice.makeRequest()
.do(myThing)
.subscribe();
}
}

Related

Redux-forms Call alternate submit from ComponentWillRecieveProps

redux-forms version: 6.6.3
react version: 15.5.0
I want to call different submit functions from componentWillRecieveProps function in my react component.
componentWillReceiveProps(nextProps) {
if (nextProps.updateTierConfigState == "ValidationFulfilled"
&& nextProps.updateMyConfigValidationClean) {
console.log('CWRP calling submit()')
//this.props.submit(); //THIS CALLS DEFAULT on FORM's onSubmit
this.props.handleSubmit(this.updateSubmit().bind(this))
}
else {
this.props.handleSubmit(this.createSubmit().bind(this))
}
}
updateSubmit(values) {
//do stuff
}
createSubmit(values) {
//do stuff
}
I have see examples like this: https://github.com/erikras/redux-form/issues/711#issuecomment-191850962
But, I have not been able to call handleSubmit successfully. It does not call the passed in function.
I have debugged into handleSubmit and it returns very quickly with out calling the specified submit function.
You are immediately invoking the functions when you pass them in. When you reference the submit functions, you don't need to include the () after the function name. this.updateSubmit().bind(this) should be this.updateSubmit. You also don't need the bind here.
Change: this.props.handleSubmit(this.updateSubmit().bind(this))
To: this.props.handleSubmit(this.updateSubmit)
I discovered that I needed to do two things.
Invoke the custom submit method using this line: this.props.handleSubmit((values)=> this.submitUpdate(values))() Note the added "()" at the end.
Move this line (and a surrounding if condition) to the componentDidUpdate() method. I'm using props in the submit method to decide the state of validation of the component and the props from mapStateToProps are not assigned to this.props before componentWillReceiveProps or componentWillUpdate.
Here is my new automatic submit call:
componentDidUpdate() {
//moved this here so that this.props is updated from nextState
if (this.props.updateMyConfigState == "ValidationFulfilled"
&& this.props.updateMyConfigValidationClean) {
this.props.handleSubmit((values) => this.submitUpdate(values))();
}
}
I am aware that this is outside of the redux-form recommendation for validation and I recognize that this this might not be the best way to handle validation. My current reasoning is:
It allows me to have multiple submission validation actions (separates ones for create and update user actions) where each has their own error and warnings behavior. In particular, Error: disallow action and WarningsOnly: allow but require extra user approval.
Visually present the errors/warnings in a separate list outside of the gui elements.

ngRx state update and Effects execution order

I have my own opinion on this question, but it's better to double check and know for sure. Thanks for paying attention and trying to help. Here it is:
Imagine that we're dispatching an action which triggers some state changes and also has some Effects attached to it. So our code has to do 2 things - change state and do some side effects. But what is the order of these tasks? Are we doing them synchronously? I believe that first, we change state and then do the side effect, but is there a possibility, that between these two tasks might happen something else? Like this: we change state, then get some response on HTTP request we did previously and handle it, then do the side effects.
[edit:] I've decided to add some code here. And also I simplified it a lot.
State:
export interface ApplicationState {
loadingItemId: string;
items: {[itemId: string]: ItemModel}
}
Actions:
export class FetchItemAction implements Action {
readonly type = 'FETCH_ITEM';
constructor(public payload: string) {}
}
export class FetchItemSuccessAction implements Action {
readonly type = 'FETCH_ITEM_SUCCESS';
constructor(public payload: ItemModel) {}
}
Reducer:
export function reducer(state: ApplicationState, action: any) {
const newState = _.cloneDeep(state);
switch(action.type) {
case 'FETCH_ITEM':
newState.loadingItemId = action.payload;
return newState;
case 'FETCH_ITEM_SUCCESS':
newState.items[newState.loadingItemId] = action.payload;
newState.loadingItemId = null;
return newState;
default:
return state;
}
}
Effect:
#Effect()
FetchItemAction$: Observable<Action> = this.actions$
.ofType('FETCH_ITEM')
.switchMap((action: FetchItemAction) => this.httpService.fetchItem(action.payload))
.map((item: ItemModel) => new FetchItemSuccessAction(item));
And this is how we dispatch FetchItemAction:
export class ItemComponent {
item$: Observable<ItemModel>;
itemId$: Observable<string>;
constructor(private route: ActivatedRoute,
private store: Store<ApplicationState>) {
this.itemId$ = this.route.params.map(params => params.itemId);
itemId$.subscribe(itemId => this.store.dispatch(new FetchItemAction(itemId)));
this.item$ = this.store.select(state => state.items)
.combineLatest(itemId$)
.map(([items, itemId]: [{[itemId: string]: ItemModel}]) => items[itemId])
}
}
Desired scenario:
User clicks on itemUrl_1;
we store itemId_1 as loadingItemId;
make the request_1;
user clicks on itemUrl_2;
we store itemId_2 as loadingItemId;
switchMap operator in our effect cancells previous request_1 and makes request_2;
get the item_2 in response;
store it under key itemId_2 and make loadingItemId = null.
Bad scenario:
User clicks on itemUrl_1;
we store itemId_1 as loadingItemId;
make the request_1;
user clicks on itemUrl_2;
we store itemId_2 as loadingItemId;
we receive the response_1 before we made the new request_2 but after loadingItemId changed;
we store the item_1 from the response_1 under the key itemId_2;
make loadingItemId = null;
only here our effect works and we make request_2;
get item_2 in the response_2;
try to store it under key null and get an error
So the question is simply if the bad scenario can actually happen or not?
So our code has to do 2 things - change state and do some side
effects. But what is the order of these tasks? Are we doing them
synchronously?
Let's say we dispatch action A. We have a few reducers that handle action A. Those will get called in the order they are specified in the object that is passed to StoreModule.provideStore(). Then the side effect that listens to action A will fire next. Yes, it is synchronous.
I believe that first, we change state and then do the side effect, but
is there a possibility, that between these two tasks might happen
something else? Like this: we change state, then get some response on
HTTP request we did previously and handle it, then do the side
effects.
I've been using ngrx since middle of last year and I've never observed this to be the case. What I found is that every time an action is dispatched it goes through the whole cycle of first being handled by the reducers and then by the side effects before the next action is handled.
I think this has to be the case since redux (which ngrx evolved from) bills itself as a predictable state container on their main page. By allowing unpredictable async actions to occur you wouldn't be able to predict anything and the redux dev tools wouldn't be very useful.
Edited #1
So I just did a test. I ran an action 'LONG' and then the side effect would run an operation that takes 10 seconds. In the mean time I was able to continue using the UI while making more dispatches to the state. Finally the effect for 'LONG' finished and dispatched 'LONG_COMPLETE'. I was wrong about the reducers and side effect being a transaction.
That said I think it's still easy to predict what's going on because all state changes are still transactional. And this is a good thing because we don't want the UI to block while waiting for a long running api call.
Edited #2
So if I understand this correctly the core of your question is about switchMap and side effects. Basically you are asking what if the response comes back at the moment I am running the reducer code which will then run the side effect with switchMap to cancel the first request.
I came up with a test that I believe does answer this question. The test I setup was to create 2 buttons. One called Quick and one called Long. Quick will dispatch 'QUICK' and Long will dispatch 'LONG'. The reducer that listens to Quick will immediately complete. The reducer that listens to Long will take 10 seconds to complete.
I setup a single side effect that listens to both Quick and Long. This pretends to emulate an api call by using 'of' which let's me create an observable from scratch. This will then wait 5 seconds (using .delay) before dispatching 'QUICK_LONG_COMPLETE'.
#Effect()
long$: Observable<Action> = this.actions$
.ofType('QUICK', 'LONG')
.map(toPayload)
.switchMap(() => {
return of('').delay(5000).mapTo(
{
type: 'QUICK_LONG_COMPLETE'
}
)
});
During my test I clicked on the quick button and then immediately clicked the long button.
Here is what happened:
Quick button clicked
'QUICK' is dispatched
Side effect starts an observable that will complete in 5 seconds.
Long button clicked
'LONG' is dispatched
Reducer handling LONG takes 10 seconds. At the 5 second mark the original observable from the side effect completes but does not dispatch the 'QUICK_LONG_COMPLETE'. Another 5 seconds pass.
Side effect that listens to 'LONG' does a switchmap cancelling my first side effect.
5 seconds pass and 'QUICK_LONG_COMPLETE' is dispatched.
Therefore switchMap does cancel and your bad case shouldn't ever happen.

Why is ugly hack required for react-redux to re-render redux-form?

I have a react app that is using a redux store and this component also uses redux-form. I am seeing a strange behavior on one particular type of update where I want to trigger a follow-on update.
Basically a user is updating permissions for something on the form, then saves. The save submits the form and in the processing of the action, it triggers another action (getting a list of all the somethings). When the list is returned from the server, this dispatches an action to update the list UI. At this point I am updating a property in the redux store for the form to say trigger an update of the permissions for the currently editing thing. This is the only point where I can do this because we now have the information to trigger this request.
The problem is that a boolean flag is being set as the trigger, and the mapStateToProps is getting called with the updated flag, but the component is not subsequently getting called (shouldComponentUpdate and componentWillUpdate never get called). It is like it ignores the state of the boolean variable in the props.
I found that if, in mapStateToProps I set another property based on the
boolean trigger, then the component is updated (re-rendered).
function mapStateToProps(store, ownProps) {
...
let triggerFetchSomethingOwnershipHash = 0;
if (store.triggerFetchSomethingOwnership) {
triggerFetchSomethingOwnershipHash = 100000 * Math.random();
}
return {
...
triggerFetchSomethingOwnership,
triggerFetchSomethingOwnershipHash
}
}
...
const ConnectedSomethingDetail = connect(mapStateToProps)(SomethingForm);
export default ConnectedSomethingDetail;
Why do I need to do this and how can I avoid it?
Yes I have checked a few relevant SO questions like react-redux-component-does-not-rerender-on-store-state-change and react-redux-update-item-in-array-doesnt-re-render

How to get the return state from a sendAction method in a component

I am not using Ember Data (I do not know if it solves the issue). I have a component and click the button will invoke an external event handler defined inside the controller. I would like to know the return state from a sendAction method. See image below for details.
enter text in textarea
click "BTN SAVE"
handle action in index controller
perform ajax request in btnPressed actions obj
MY QUESTION How to notify the component the return state from the step (4)
Once I know the request state I can perform the correspond action(s) in my component.
Thanks
You can't have return value from action if you're using old action handling mechanism(using sendAction()), what you need are closure actions. Using closure actions you can call action defined in controller directly in component and have return value from it.
Here's your example using closure actions.
index.hbs
{{note-editor btnPressed=(action 'btnPressed')}}
In note-editor.hbs
<button onclick={{action 'actionBtnPressed'}}>Save</button>
And then in note-editor.js you can call btnPressed when button is clicked.
actionBtnPressed() {
let val = this.btnPressed();
// val is value returned from controller's btnPressed action
}
You can read more about closure actions here.
https://github.com/emberjs/rfcs/blob/master/text/0050-improved-actions.md
After reading the post Correct way to make an ajax call from EmberJs component?,
I move the AJAX code inside the controller to the component to solve this issue.
actionBtnPressed() {
const card = {
front: this.get('cardFront'),
back: this.get('cardBack'),
tags: this.get('tags')
};
if (this._validateCardFront()) {
this.get('ajax').createCard(card)
.done((data) => {
this._cleanInputs();
})
.fail((err) => {
});
}
}
ajax is a service that is injected into the component.

Wait for other interaction to happen before resolving RSVP

I have a component that on a swipe will send an action upward to the parent route/controller to handle some ajax functionality. This component has some UI that gets set to a loading state to show the user things are happening. When working gets set back to false the loading animation in UI stops and user interaction can happen again.
callAjaxAction() {
this.setProperties({working:true});
Ember.RSVP.cast(this.attrs.action()).finally(() => {
this.$('.draggable').animate({
left: 0
});
this.setProperties({working:false});
});
}
In this case the controller catches the action specified on the component definition and calls an ajax function to get some data to display in the page
// in the controller action
return Ember.RSVP.Promise((resolve,reject) => {
Ember.$.ajax({
type: 'get',
dataType: 'json',
url: `http://***/api/paysources/user/697?key=${ENV.APP.api_key}`
}).then((response)=>{
this.setProperties({
'cards':response.user_paysources,
'showCards': true
});
},(reason)=>{
reject();
this.get('devlog').whisper(reason);
})
})
This will show a new popup type of component that will allow the user to pick a card to pay with. If the user clicks away, or if they click a card and the ajax action completes I need to reset the UI not only on this page (change it so it says the cart has been paid for) but also send the swipe component (the one that now has a loading animation) something that tells it it's done loading.
Basically, the way I see it in my head, is there a way to fire an action on a component from the parent controller/route?
To answer your question "is there a way to fire an action on a component from the parent controller/route?": No there is not. However there are two idiomatic ways I can think of for doing this in Ember:
You could potentially get around it by moving the Ajax request into the component.
Follow the 'data down, actions up' pattern. See the following as an example of how to implement it.
You could fire an action on the card component that changes a property on your controller. Then listen to that property into the original component so it can update.
original-component.js
reset: computed('transactionComplete', function() {
// cleanup stuff here...
})
original-component-template.hbs
{{#if transactionComplete}}
{{! stuff to show when transaction is complete... }}
{{/if}}
controller.js
transactionComplete: false,
actions: {
completeTransaction() {
this.toggleProperty('transactionComplete');
}
}
controller-template.hbs
{{original-component
transactionComplete=transactionComplete
}}
{{cart-component
transactionComplete=(action 'completeTransaction')
}}
cart-component.js
actions: {
processTransaction() {
// cleanup stuff here...
this.attrs.transactionComplete();
}
}
There might be different – and better – ways of doing this, but it depends on exactly what you need to do when resetting the original component.
Also, separately, have you considered using ember data and routing for loading the cards?

Categories

Resources