What's the proper way to dispatch action inside epic? - javascript

What is the proper way to dispatch operationReset() inside of redux-observable epic?
Should I import actual store and use it?
It used to be like this, but following store is deprecated, and will be removed
// show operation failed message
(action$, store) => action$.ofType(OPERATION_FAILURE).map(() => (error({
title: 'Operation Failed',
message: 'Opps! It didn\'t go through.',
action: {
label: 'Try Again',
autoDismiss: 0,
callback: () => store.dispatch(operationReset())
}
}))),

This probably raises a larger question about how one should do notifications with callbacks, since it means you're sending a non-JSON serializable function as part of an action.
I'll assume you want to match the react notification system still. There's a way you can do this using Observable.create:
(action$, store) =>
action$.pipe(
ofType(OPERATION_FAILURE),
mergeMap(() =>
Observable.create(observer => {
observer.next(
error({
title: "Operation Failed",
message: "Oops! It didn't go through.",
action: {
label: "Try Again",
autoDismiss: 0,
callback: () => {
// Send off a reset action
observer.next(operationReset());
// Close off this observable
observer.complete();
},
// If the notification is dismissed separately (can they click an x?)
onRemove: () => observer.complete()
}
})
);
})
)
);
NOTE: I still wouldn't want to send callbacks as part of actions. Amusingly, one of my projects uses that notification system component too -- we have epics that will add notifications and clear them based on actions. All actions stay pure, and the notification system is a controlled side effect.

Related

Posting result data to a website (like API or a telegram bot) after a test on Cypress

I've finished writing my first Cypress test. Everything is good except I'm struggling to post the result data to a website. Because I want to send the result data and also if any errors occurs the result screenshot to our coworker telegram group.
For the last two days I've tried everything and couldn't find any solution.
I've tried those in my test script (cypress/integration/test.js);
Cypress.on('test:after:run', (test, runnable) => {
console.log('test,runnable', test, runnable)
const details = {
projectKey: Cypress.env('zephyr-project-key'),
testName: test.invocationDetails.relativeFile,
status: test.status,
error: runnable.err.message,
retries: runnable.retries.length,
duration: test.wallClockDuration,
startTime: test.wallClockStartedAt
}
cy.request('POST', 'http://mywebsite.com/notify.php', { body: details })
fetch('http://mywebsite.com/notify.php')
})
Also this didn't work (cypress/plugins/index.js);
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
on('after:run', (results) => {
if (results) {
// results will be undefined in interactive mode
console.log(results.totalPassed, 'out of', results.totalTests, 'passed')
fetch('http://mywebsite.com/notify.php');
}
})
}
Edit: This is day 3 and I still couldn't solve this. What I've seen from Cypress help page is that cy.task() calls do not fire in 'test:after:run' event block;
https://github.com/cypress-io/cypress/issues/4823
I've seen some telegram groups who can do what I'm trying to do. All I need is to be able to get the results and post it to my website.
The third parameter to cy.request() is body, you don't have to wrap it.
Cypress.on('test:after:run', (test, runnable) => {
const details = {
projectKey: Cypress.env('zephyr-project-key'),
testName: test.invocationDetails.relativeFile,
status: test.status,
error: runnable.err?.message, // need err?.message if there is no error
retries: runnable.retries.length,
duration: test.wallClockDuration,
startTime: test.wallClockStartedAt
}
cy.request('POST', 'http://mywebsite.com/notify.php', details) // don't wrap details
.then(res => expect(res.status).to.eq(201)) // confirm result
})

Vue continue on successful callback

I have a Vue app with Vuex dependency. Program logic is that message should be sent after successful Facebook share.
This is the method getting triggered on button click for sending the message:
onSubmit() {
if(this.shouldShowShareModal){
this.$store.dispatch('openModal', 'SHARE_MODAL');
return;
}
this.$store.dispatch('sendMessage', {
conversation_id: this.conversationId,
message : this.messageText,
message_type_id: 1
}).then(() => {
this.messageText = '';
this.scrollToBottom();
});
},
openModal simply sets the value of the given modal to true, thus v-if shows the modal which has the button which upon share method triggers a Facebook share:
share() {
var self = this;
FB.ui({
method: 'share',
href: 'https://developers.facebook.com/docs/'
}, function(){
self.$store.dispatch('sharedThroughFacebook');
self.$store.dispatch('closeModal', 'SHARE_MODAL');
});
}
Now the issue I have is how can I continue sending the message after Facebook callback is successful? Currently with sharedThroughFacebook I am simply setting the store sharing flag to true, but I am not sure about the best approach for sending the message only after a successful callback? If I push the data to modal, that seems like a dirty solution and that modal should not be aware of message state. On the other hand putting the conversation ID, message, type and text on Vuex store seems like an overhead since this component is the only one using the data.
You can compose your action like this
actions: {
async share ({ dispatch, commit }) {
await dispatch('sharedThroughFacebook') // wait for `sharedThroughFacebook` to finish
await dispatch('closeModal') // close the modal before sending the message
commit('sendMesseage')
},
async sharedThroughFacebook (context, payload) {
FB.ui({
method: 'share',
href: 'https://developers.facebook.com/docs/'
})
}
}

How can I make a Switch component async await for successful response before changing value?

The default flow of a switch will toggle the value as soon as it is pressed.
I want to execute the following logic
Press switch
Don't change the value yet
Open alert box
If ok, send post request to toggle database value
On successful response, allow the switch value to change
I can get the successful response and update the switch, but as soon as I press the switch the value toggles twice and I want to pause this behaviour until there is a successful response. Tried async await, tried wrapping the switch in a touchablewithout feedback... now need some help
<Switch
value={this._getSwitchValue(id)}
onValueChange={() => this._toggleSwitch(id)}
/>
/**
* Toggle the Switch - Send post request to database
*/
_toggleSwitch (id) {
Alert.alert('Notice!', 'Do you really want to change this setting?',
[
{ text: "OK", onPress: () => this.props.toggleSettingsSwitch(id, this.props.token) },
{ text: "Cancel", onPress: () => console.log("cancel pressed")}
],
{ cancelable: true }
)}
Change the Switch value inside the function that makes the request.
toggleSettingsSwitch = async (value) => {
const response = await this.changeSettingsRequest(); // making a call
// here add a check, if request was successful change switch value
// if (response.ok) {
// this.setState({ switchValue: value });
//}
};
_toggleSwitch = (value) => {
Alert.alert(
'Notice!',
'Do you really want to change this setting?',
[
{
text: 'OK',
onPress: () => {
this.toggleSettingsSwitch(value);
},
},
{ text: 'Cancel', onPress: () => console.log('cancel pressed') },
],
{ cancelable: true }
);
};
<Switch
onValueChange={this._toggleSwitch}
value={this.state.switchValue}/>
snack example
I know this is an old question, but for people still looking for an answer to this, try: react-native-switch-pro
Another alternative is to create your own async-supporting switch-like component, here's an example on snack: Custom Async Switch
You can just have another variable loading and ignore calls, if it is true
_toggleSwitch = async (value) => {
if (this.loading) {
return;
}
this.loading = true;
try {
...
} finally {
this.loading = false;
}
};
You can even put it in React state and make it part of UI interaction (hiding Switch if it's in loading state and presenting some kind of animation)
I would just optimistically update UI state and inform user in case of an error, that way UI would feel more responsive.

redux-observable - wait until request finishes before starting another one

I have a list of invites in my app, each one with corresponding delete button. When a user clicks delete a DELETE_INVITE action is dispatched and an epic fire:
const deleteInvite = (action$: any, store: Store<ReduxState, *>) =>
action$.pipe(
ofType(DELETE_INVITE),
mergeMap(({ payload }) =>
ajax(api.deleteInvite(payload.inviteId)).pipe(
map((response: Object) => ({
type: DELETE_INVITE + SUCCESS,
payload: {
data: response.response,
status: response.status,
},
})),
catchError((error: Object) => of({
type: DELETE_INVITE + FAILURE,
error: {
response: {
data: error.xhr.response,
status: error.xhr.status,
},
},
})),
),
),
);
Now I would like to ensure that only one request is fired at the time and wait until last one finished. In other words, I want to protect myself from a situation where user rapidly clicks all over the buttons and fires few request simultaneously.
switchMap is kind of something I'm looking for because it would handle only most recent click... but the request would be already fired and UI left with outdated data. So I need something that will let call mergeMap again only when inner chain completes.
Based on your comments below, it sounds like what you want is exhaustMap.
Projects each source value to an Observable which is merged in the output Observable only if the previous projected Observable has completed.
const deleteInvite = (action$: any, store: Store<ReduxState, *>) =>
action$.pipe(
ofType(DELETE_INVITE),
exhaustMap(({ payload }) =>
ajax(api.deleteInvite(payload.inviteId)).pipe(
map((response: Object) => ({
type: DELETE_INVITE + SUCCESS,
payload: {
data: response.response,
status: response.status,
},
})),
catchError((error: Object) => of({
type: DELETE_INVITE + FAILURE,
error: {
response: {
data: error.xhr.response,
status: error.xhr.status,
},
},
})),
),
),
);
I guess I would ask why you need to use redux-observable to achieve this in the first place. Can't you just set some state variable in your redux store at the start of your request (like deleteInProgress = true), and use this state to disable the delete button. And when your request finishes (either successfully or erroroneously), set the deleteInProgress flag back to false, which will re-enable the button.

Smooch: How to do postback dependent state transition?

I am trying to transition the script from one state to another based on Smooch postback payloads; but getting error code H12.
Consider the example https://github.com/smooch/smooch-bot-example
Say I modify the script https://github.com/smooch/smooch-bot-example/blob/master/script.js as follows
start: {
receive: (bot) => {
return bot.say('Hi! I\'m Smooch Bot! Continue? %[Yes](postback:askName) %[No](postback:bye) );
}
},
bye: {
prompt: (bot) => bot.say('Pleasure meeting you'),
receive: () => 'processing'
},
The intention is that the's bot's state would transition depending on the postback payload.
Question is, how do I make that happen?
My approach was add
stateMachine.setState(postback.action.payload)
to the handlePostback method of github.com/smooch/smooch-bot-example/blob/master/heroku/index.js
However, that threw an error code H12. I also experimented with
stateMachine.transition(postback.action,postback.action.payload)
to no avail.
I got the same issue with the [object Object] instead of a string. This is because the state you get or set with a function is contained in an object, not a string... I fixed it with this code inside index.js, replacing the existing handlePostback function in the smooch-bot-example GitHub repo:
function handlePostback(req, res) {
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
const postback = req.body.postbacks[0];
if (!postback || !postback.action) {
res.end();
};
const smoochPayload = postback.action.payload;
// Change conversation state according to postback clicked
switch (smoochPayload) {
case "POSTBACK-PAYLOAD":
Promise.all([
stateMachine.bot.releaseLock(),
stateMachine.setState(smoochPayload), // set new state
stateMachine.prompt(smoochPayload) // call state prompt() if any
]);
res.end();
break;
default:
stateMachine.bot.say("POSTBACK ISN'T RECOGNIZED") // for testing purposes
.then(() => res.end());
};
}
Then inside script.js all you need to do is define states corresponding to the exact postback payloads. If you have multiple postbacks that should take the user to other states, just add them to the case list like so :
case "POSTBACK-PAYLOAD-1":
case "POSTBACK-PAYLOAD-2":
case "POSTBACK-PAYLOAD-3":
case "POSTBACK-PAYLOAD-4":
Promise.all([
stateMachine.bot.releaseLock(),
stateMachine.setState(smoochPayload), // set new state
stateMachine.prompt(smoochPayload) // call state prompt() if any
]);
res.end();
break;
Note that you should not write break; at the end of each case if the outcome you want is the same (here : setting the state and prompting the corresponding message).
If you want to handle other postbacks differently, you can add cases after the break; statement and do other stuff instead.
Hope this helps!
Postbacks won't automatically transition your conversation from one state to the next, you have to write that logic yourself. Luckily the smooch-bot-example you're using already has a postback handler defined here:
https://github.com/smooch/smooch-bot-example/blob/30d2fc6/heroku/index.js#L115
So whatever transition logic you want should go in there. You can do this by creating a stateMachine and calling receiveMessage() on it the same way handleMessages() already works. For example:
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
stateMachine.receiveMessage({
text: 'whatever your script expects'
})
Alternatively, you could have your handlePostback implementation call stateMachine.setState(state) and stateMachine.prompt(state) independently, if you wanted to have your postbacks behave differently from regular text responses.
If you want to advance the conversation based on a postback you'll have to first output the buttons from the bot's prompt (so you can handle the button click in the receive), modify the handlePostback function in index.js, then handle the user's "reply" in your receive method - try this - modify script.js like so:
start: {
prompt: (bot) => bot.say(`Hi! I'm Smooch Bot! Continue? %[Yes](postback:askName) %[No](postback:bye)`),
receive: (bot, message) => {
switch(message.text) {
case 'Yes':
return bot.say(`Ok, great!`)
.then(() => 'hi')
break;
case 'No':
return bot.say(`Ok, no prob!`)
.then(() => 'bye')
break;
default:
return bot.say(`hmm...`)
.then(() => 'processing')
break;
}
}
},
hi: {
prompt: (bot) => bot.say('Pleasure meeting you'),
receive: () => 'processing'
},
bye: {
prompt: (bot) => bot.say('Pleasure meeting you'),
receive: () => 'processing'
},
Then modify the handlePostback function in index.js so that it treats a postback like a regular message:
function handlePostback(req, res) {
const postback = req.body.postbacks[0];
if (!postback || !postback.action)
res.end();
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
const msg = postback;
// if you want the payload instead just do msg.action.paylod
msg.text = msg.action.text;
stateMachine.receiveMessage(msg)
.then(() => res.end())
.catch((err) => {
console.error('SmoochBot error:', err);
res.end();
});
}
Now when a user clicks your button it will be pushed to the stateMachine and handled like a reply.

Categories