xstate: Promises: how to detect network error and exit state - promise

I have an xstate state machine in javascript. I invoke a promise on entering a state and show a spinning wheel while the promise is being resolved. I would like to detect network error and send a NETWORK_ERROR event to my state machine. How do I do this?
My promise call:
function connectToSomeEntity(waitTimeInMilliSeconds) {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
/*do something like try connecting to a database*/
resolve(0);
} catch (error) { reject('[ErrorCode:-1] Unable to connect to server.'); }
}, Number(waitTimeInMilliSeconds));
});
}
my xstate state machine:
WAITFORCONNECT: {
activities: ["showingLoadingPanel"],
on: {
DISCONNECTED: 'DISCONNECT',
LOGIN_SUCCESS: 'CONNECTED',
LOGIN_FAILURE: {
target: 'showingAlert',
actions: assign({ errorMessage: (context, event) => event.data })
},
LOGOUT: {
target: 'showingFinalAlert',
actions: assign({ errorMessage: (context, event) => event.data })
},
CLOSEWINDOW: {
target: 'showingFinalAlert',
actions: assign({ errorMessage: (context, event) => event.data })
}
},
invoke: {
src: (context, event) =>
connectToSomeEntity(context.waitTimeInMilliSeconds),
onError: {
target: "showingAlert",
actions: assign({ errorMessage: (context, event) => event.data }),
},
},
}

Related

Vue 3 components not awaiting for state to be loaded

I am having some trouble using fetch in vuex to build state before rendering my page's components.
Here is the page component code:
async beforeCreate() {
await this.$store.dispatch('projects/getProjects');
},
And this is the state code it's dispatching:
async getProjects(context: any, parms: any) {
context.commit("loadingStatus", true, { root: true });
console.log("1");
await fetch(`${process.env.VUE_APP_API}/projects?`, {
method: "get",
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
console.log("2");
if (!response.ok) {
throw new Error(response.status.toString());
} else {
return response.json();
}
})
.catch((error) => {
// todo: tratamento de erros na UI
console.error("There was an error!", error);
})
.then((data) => {
context.commit("setProjects", { data });
console.log("3");
// sets the active project based on local storage
if (
localStorage.getItem(
`activeProjectId_${context.rootState.auth.operator.accountId}`
)
) {
console.log("setting project to storage");
context.dispatch("selectProject", {
projectId: localStorage.getItem(
`activeProjectId_${context.rootState.auth.operator.accountId}`
),
});
} else {
//or based on the first item in the list
console.log("setting project to default");
if (data.length > 0) {
context.dispatch("selectProject", {
projectId: data[0].id,
});
}
}
context.commit("loadingStatus", false, { root: true });
});
},
async selectProject(context: any, parms: any) {
console.log("4");
context.commit("loadingStatus", true, { root: true });
const pjt = context.state.projects.filter(
(project: any) => project.id === parms.projectId
);
if (pjt.length > 0) {
console.log("Project found");
await context.commit("setActiveProject", pjt[0]);
} else if (context.state.projects.length > 0) {
console.log("Project not found setting first on the list");
await context.commit("setActiveProject", context.state.projects[0]);
} else {
await context.commit("resetActiveProject");
}
await context.commit("loadingStatus", false, { root: true });
},
I've added this console.log (1, 2, 3, 4) to help me debug what's going on.
Right after console.logging "1", it starts to mount the components. And I only get logs 2, 3 and 4 after all components have been loaded.
How can I make it so that my components will only load after the whole process is done (i.e. after I log "4") ?
If your beforeCreate hook (or any client hooks) contains async code, Vue will NOT wait to it then render and mount the component.
The right choice here should be showing a loader when your data is fetching from the server. It will provide better UX:
<template>
<div v-if="!data"> Loading... </div>
<div v-else> Put all your logic with data here </div>
</template>
<script>
export default {
data() {
return {
data: null
}
},
async beforeCreate() {
this.data = await this.$store.dispatch('projects/getProjects');
},
}
</script>

AngularFireAuth signInWithEmailAndPassword callback is never thrown when deploying for iOS using Ionic Capacitor

I built a webapp using Ionic 3, Angular 12, Firebase 9, AngularFire.
When I try to authenticate (on iOS) the callback is never thrown from this function and therefore the authentication is never performed although the requests are executed with a status code of 200. On the web the authentication works as intended.
import { AngularFireAuth } from "#angular/fire/compat/auth";
...
async loginWithEmail(email: string, password: string) : Promise<void> {
return new Promise<User>((resolve, reject) =>
this.afAuth.signInWithEmailAndPassword(email, password).then(
(res) => {
resolve(res.user);
},
(error: { message: string }) => {
reject(error);
this.toastService.present({
message: "Something has gone wrong. Please try again.",
duration: 3000,
color: "warning"
});
console.error(error.message);
}
)
).then(
(result: User) => {
if (result) {
this._user.next(result);
} else {
if (result != null) {
this.zone.run(() => {
void this.router.navigate(['tabs', 'home']);
});
}
}
},
(error: { message: string }) => {
this.toastService.present({
message: error.message,
duration: 3000,
color: "warning"
});
}
);
}
What can I do about this behaviour?

React - can't dispatch action in hook component

Using dispatch in useffect hook of functional component,
Below code shows error page like below;
Component:
import { GetParks } from "../../../redux/actions/survey_actions"
...
function BarcodeGenerator(props) {
const dispatch = useDispatch();
useEffect(() => {
dispatch(props.GetParks());
}, []);
actions:
export const GetParks = (Id) => async (dispatch, getState) => {
try {
const response = await axiosHelper.get("api/survey/GetParks", {
params: {
Id,
},
});
debugger;
response = response.data;
if (response.status !== ResponseStatus.SUCCESS) {
dispatch({
type: GET_PARKS,
payload: [1, 4555, 34],
});
}
} catch (error) {
catchCallback(error);
}
};
const _getParks = (data) => ({
type: GET_PARKS,
payload: data,
});
how does dispatch the action to reducer properly
Action must be a plain object, as it is described in the error description. E.g. it is ok to use dispatch directly as is:
if (*statement*) {
dispatch({
action: DO_SMTH,
payload: true
})
}
or to make the action creator returning the equal object if you want to make clean re-usable code:
if (*statement*) {
dispatch(doSmth(true));
}
function doSmth(toggle) {
return ({
action: DO_SMTH,
payload: toggle
})
}

How to access object from mounted hook in Vuejs

I want to access data object in mounted hook, but when I try to access the data it will throw undefine in the console.
This is my source code
export default {
data() {
return {
channel: {},
subscription: {},
}
},
methods: {
read() {
axios.get('/api/get/details').then(({ data }) => {
this.channel= data;
})
.catch((err) => console.error(err));
},
},
mounted() {
this.read();
console.log(this.channel.data.userid)
fetch("https://url/v1/launch/1/details")
.then(response => response.json())
.then(json => {
this.subscription = json
});
}
}
but when I console this.channel.data.userid I gat 'undefine'
Your code is asynchronous, you meaning that console.log does not wait until this.read() is finished. Changing it to the following should work.
export default {
data() {
return {
channel: {},
subscription: {},
}
},
methods: {
async read() {
const { data } = await axios.get('/api/get/details')
this.channel = data;
},
},
async mounted() {
await this.read();
console.log(this.channel.data.userid)
fetch("https://url/v1/launch/1/details")
.then(response => response.json())
.then(json => {
this.subscription = json
});
}
}
Read up more on async and Promise
You are having a synchronous problem. Make your functions asynchronous and wait to end it.
export default {
data() {
return {
channel: {},
subscription: {},
}
},
methods: {
async read() {
await axios.get('/api/get/details').then(({ data }) => {
this.channel= data;
})
.catch((err) => console.error(err));
},
},
async mounted() {
await this.read();
console.log(this.channel.data.userid);
fetch("https://url/v1/launch/1/details")
.then(response => response.json())
.then(json => {
this.subscription = json
});
}
}

Moxios Requests State Not Cleared In Between Tests

My specs are behaving weirdly in that when I run the tests alone, they pass. However, when I run the test suite all together, the failure tests still continue to use the success axios mock instead of using the correct failing http axios mock. This results in my tests failing. Am I missing something for isolating the 2 mocks from each other in the different portions of code?
jobactions.js
export const loadUnassignedJobs = (job_type) => {
if (!['unscheduled', 'overdue'].includes(job_type)) {
throw 'Job Type must be "unscheduled" or "overdue".';
}
return (dispatch) => {
dispatch({type: JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED, job_type });
return axios.get(defaults.baseapi_uri + 'jobs/' + job_type)
.then(function (response) {
dispatch(updateUnassignedJobs(response.data.jobs));
// handle success
})
.catch(function (error) {
// handle error
dispatch({ type: JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE, error });
})
.then(function () {
// always executed
});
}
};
export const updateUnassignedJobs = (unassigned_jobs) => {
let unassigned_job_ids = [];
let jobs = {};
for (let job of unassigned_jobs) {
unassigned_job_ids.push(job.id);
jobs[job.id]=job;
}
return({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS,
jobs,
unassigned_job_ids,
});
};
spec.js
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import * as jobActions from "../../../app/javascript/actions/JobActions"
import { findAction } from '../support/redux_store'
import * as JobActionTypes from '../../../app/javascript/constants/JobActionTypes'
import fixtures_jobs_unscheduled_success from '../fixtures/jobs_unscheduled_success'
import moxios from "moxios";
export const mockStore = configureMockStore([thunk]);
let store;
describe ('loadUnassignedJobs', () => {
context('when bad parameters are passed', async () => {
it('will raise an error', () => {
const store = mockStore();
expect(() => {
store.dispatch(jobActions.loadUnassignedJobs('wrong_type'));
}).to.throw('Job Type must be "unscheduled" or "overdue".');
});
});
context('when unscheduled is passed', () => {
beforeEach(() => {
moxios.install();
console.log("before each called");
console.log(moxios.requests);
store = mockStore();
store.clearActions();
});
afterEach(() => {
console.log("after each called");
console.log(moxios.requests);
moxios.uninstall();
});
context('on success', () => {
beforeEach(() => {
moxios.wait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: fixtures_jobs_unscheduled_success
});
});
})
it('dispatches LOAD_UNASSIGNED_JOBS_STARTED', () => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED)).to.be.eql({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED,
job_type: 'unscheduled'
});
});
});
it('dispatches updateUnassignedJobs()', () => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store,JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS)).to.be.eql(jobActions.updateUnassignedJobs(fixtures_jobs_unscheduled_success.jobs))
});
});
});
context('on error', () => {
beforeEach(() => {
//console.log("before each on error called");
//console.log(moxios.requests);
moxios.wait(() => {
console.log('after waiting for moxios..')
console.log(moxios.requests);
let request = moxios.requests.mostRecent();
request.respondWith({
status: 500,
response: { error: 'internal server error' }
});
});
})
it('dispatches LOAD_UNASSIGNED_JOBS_FAILURE', (done) => {
console.log(moxios.requests);
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
console.log(moxios.requests);
console.log(store.getActions());
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE)).to.include({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE
});
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE).error).to.include({
message: 'Request failed with status code 500'
});
done();
});
});
it('does not dispatch LOAD_UNASSIGNED_JOBS_SUCCESS', (done) => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS)).to.be.undefined;
done();
});
});
})
});
});
describe('updateUnassignedJobs', () => {
it('assigns jobs to hash and creates an unassigned_job_ids array', () => {
expect(jobActions.updateUnassignedJobs([ { id: 1, step_status: 'all_complete' }, { id: 2, step_status: 'not_started' } ])).to.be.eql(
{
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS,
jobs: { 1: { id: 1, step_status: 'all_complete' }, 2: { id: 2, step_status: 'not_started' } },
unassigned_job_ids: [ 1,2 ]
}
)
});
});
Found the issue!
The it() blocks for the success case were not using the done callback causing the afterEach() moxios.uninstall() to be called prematurely and not resetting the requests after the call was complete. Fixing this, and now all the tests pass.

Resources