Redux Chain Multiple Ajax Promises in one Action - redux

I have a Redux action which needs to make 2 subsequent ajax calls.
The first calls googlemaps api: https://maps.googleapis.com/maps/api/geocode/json?address=${searchTerm}&key=${gmapsKey}
The second calls a local service based on those results
/api/content/stores/byDistance/${lat},${lng}/sort
I'm using superagent to do the ajax calls. Clearly I'm experience difficulties keeping track of the promises, and including failures.
Am I mis-undestanding a core concept of Promises? Is there a simpler way to write the below?
export function loadBySearch(searchTerm) {
const geoSearchUrl = `https://maps.googleapis.com/maps/api/geocode/json?address=${searchTerm}&key=${gmapsKey}`;
return {
types: [LOAD, LOAD_BY_LAT_LONG, LOAD_FAIL],
//Do I need to make this promise here?
promise: (client) => {
const promise = new Promise( (resolve, reject) => {
console.info('making google geocode request', geoSearchUrl);
superagent.get(geoSearchUrl)
.set('Accept', 'application/json')
.then( (successData1) =>{
const results = successData1.body.results;
if (!results.length) {
reject(`no results found for this search : ${searchTerm}`);
return;
}
const lat = results[0].geometry.location.lat;
const lng = results[0].geometry.location.lng;
const path = `/api/content/stores/byDistance/${lat},${lng}/sort`;
client.get(path).then(
(successData2) => {
resolve( {
searchTerm: searchTerm,
searchLocation: {
lat,
lng
},
data: successData2
});
},
(errorData2) => {
reject( {
searchTerm: searchTerm,
result: errorData2
});
},
);
},
(errorData1) => {
reject({
searchTerm: searchTerm,
result: errorData1
});
}
);
});
return promise;
}
};
}

I'm not using superagent, but I'm guessing something like this might just work:
superagent.get(geoSearchUrl)
.set('Accept', 'application/json')
.then(successData1 => {
const results = successData1.body.results;
if (!results.length) {
throw(`no results found for this search : ${searchTerm}`);
}
return Promise.resolve(results);
})
.then(results => {
const lat = results[0].geometry.location.lat;
const lng = results[0].geometry.location.lng;
const path = `/api/content/stores/byDistance/${lat},${lng}/sort`;
return client.get(path);
})
.then(successData2 => {
return Promise.resolve({
searchTerm: searchTerm,
searchLocation: {
lat,
lng
},
data: successData2
});
})
.catch(error => {
return Promise.reject({
searchTerm: searchTerm,
result: error
});
});
Haven't test it, but I hope at least it helps ;)

Related

Dispatch function seems to run more than one time after I test on stand alone app expo-deeplink

It sounds so weird to me and I have no idea what's wrong here because everything is fine in a development environment. So the way app works are simple, user sign in, choose it's therapist then pay for it and after successful payment, booking is confirmed, but the problem is booking is being booked exactly 3 times in firebase real-time database no matter what and I don't know why... (in the development area all is fine and it's gonna book just once as the user requested)
here's my code of booking:
const bookingHandler = () => {
Linking.openURL('http://www.medicalbookingapp.cloudsite.ir/sendPay.php');
}
const handler = (e) => handleOpenUrl(e.url);
useEffect(() => {
Linking.addEventListener('url', handler)
return () => {
Linking.removeEventListener('url', handler);
}
});
const handleOpenUrl = useCallback((url) => {
const route = url.replace(/.*?:\/\/\w*:\w*\/\W/g, '') // exp://.... --> ''
const id = route.split('=')[1]
if (id == 1) {
handleDispatch();
toggleModal();
} else if (id == 0) {
console.log('purchase failed...');
toggleModal();
}
});
const handleDispatch = useCallback(() => {
dispatch(
BookingActions.addBooking(
therapistId,
therapistFirstName,
therapistLastName,
selected.title,
moment(selectedDate).format("YYYY-MMM-DD"),
selected.slots,
)
);
dispatch(
doctorActions.updateTherapists(therapistId, selected.slots, selectedDate, selected.title, selectedPlanIndex, selectedTimeIndex)
);
setBookingConfirm(true)
})
booking action:
export const addBooking = (therapistId, therapistFirstName, therapistLastName, sessionTime, sessionDate, slotTaken) => {
return async (dispatch, getState) => {
let userId = firebase.auth().currentUser.uid
const confirmDate = moment(new Date()).format("ddd DD MMMM YYYY")
const response = await fetch(
`https://mymedicalbooking.firebaseio.com/bookings/${userId}.json`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
userId,
therapistId,
confirmDate,
therapistFirstName,
therapistLastName,
sessionTime,
sessionDate,
slotTaken
})
}
);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const resData = await response.json();
dispatch({
type: ADD_BOOKING,
bookingData: {
userId: userId,
therapistId: therapistId,
therapistFirstName: therapistFirstName,
therapistLastName: therapistLastName,
sessionTime: sessionTime,
sessionDate: sessionDate
}
});
};
};
Booking reducer:
const initialState = {
bookings: [],
userBookings: []
};
export default (state = initialState, action) => {
switch (action.type) {
case ADD_BOOKING:
const newBooking = new Booking(
action.bookingData.id,
action.bookingData.therapistId,
action.bookingData.therapistFirstName,
action.bookingData.therapistLastName,
action.bookingData.bookingdate
);
return {
...state,
bookings: state.bookings.concat(newBooking)
};
case FETCH_BOOKING:
const userBookings = action.userBookings;
return {
...state,
userBookings: userBookings
};
}
return state;
};
also, I use expo, SDK 38, Firebase as a database.
I really need to solve this, please if you have any idea don't hesitate to leave a comment or answer all of them kindly appreciated.
UPDATE:
I commented out all deep linking functionality and test the result, it's all fine. so I think the problem is with the eventListener or how I implemented my deep linking code but I still don't figure out what's wrong with the code that does fine in expo and has a bug in stand-alone.
UPDATE 2
I tried to add dependency array as suggested but still I have same problem..
there is an issue in expo-linking which on the standalone detached android app: event url fires multiple times ISSUE
I just wrapped my handling function in lodash's debounce with 1000ms wait
install lodash like this
yarn add lodash
import _ from 'lodash';
const handleOpenUrl = _.debounce((event) => {
// here is other logic
},1000);
here is your code
just add an empty dependency array into useEffect and use useCallback like this
useEffect(() => {
Linking.addEventListener('url', handleOpenUrl)
return () => {
Linking.removeEventListener('url', handleOpenUrl);
}
},[]); //like this []
const handleOpenUrl = _.debounce((url) => {
const route = url.replace(/.*?:\/\/\w*:\w*\/\W/g, '') // exp://.... --> ''
const id = route.split('=')[1]
if (id == 1) {
handleDispatch();
toggleModal();
} else if (id == 0) {
console.log('purchase failed...');
toggleModal();
}
},1000); //like this []
const handleDispatch = useCallback(() => {
dispatch(
BookingActions.addBooking(
therapistId,
therapistFirstName,
therapistLastName,
selected.title,
moment(selectedDate).format("YYYY-MMM-DD"),
selected.slots,
)
);
dispatch(
doctorActions.updateTherapists(therapistId, selected.slots, selectedDate, selected.title, selectedPlanIndex, selectedTimeIndex)
);
setBookingConfirm(true)
},[selected])

alexa sdk: saving sessionAttributes in https response

i have a skill and i want to load some data from an url and store it in the SessionAttributes.
so i wrote this into my handle(handlerInput)of my LaunchRequestHandler:
require('https').get(url, (resp) => {
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
let attributes = JSON.parse(data);
console.log(attributes);
handlerInput.attributesManager.setSessionAttributes(attributes);
});
});
the log shows me the correct object, but when i try to load the sessionAttributes in the next intent it's empty. I Assume it has something to do with the setSessionAttributes being in the response function, because if i set something directly after this code, it works. Any ideas?
This might be because of the asynchronous operation. Please use async/await to make the API call and then save. Sample example,
const getData = () => {
return new Promise((resolve, reject) => {
require("https").get(url, resp => {
resp.on("data", chunk => {
data += chunk;
});
resp.on("end", () => {
resolve(data);
});
});
});
};
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "LaunchRequest";
},
async handle(handlerInput) {
const speechText = "welcome";
const data = await getData(); //asynchronous operation
const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
sessionAttributes.data = data;
handlerInput.attributesManager.setSessionAttributes(sessionAttributes);
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withSimpleCard("Welcome to the Skill", speechText)
.getResponse();
}
};

shouldn't I dispatch an action inside a .then statement?

I found a code on git which I'm trying to understand and in the code the guy have this function:
export function startAddTodo(text) {
return (dispatch, getState) => {
const UID = firebase.auth().currentUser.uid;
const todo = {
text,
isDone: false,
isStarred: false
};
const todoRef = firebaseRef.child(`todos/${UID}`).push(todo);
dispatch(addTodo({
id: todoRef.key,
...todo
}));
todoRef.then(snapshot => {
return;
}, error => {
Alert.alert(JSON.stringify(error.message));
});
};
}
Why shouldn't it be like
const todoRef = firebaseRef.child(`todos/${UID}`).push(todo);
todoRef.then(snapshot => {
dispatch(addTodo({
id: snapshot.key,
...todo
}));
})
I think this because the promise may be rejected, but in the first code he may get an error when trying to call todoRef.key inside the dispatch method.

Observable from 2 promises

I think my solution is in this question but I can't get it to work Promise.all behavior with RxJS Observables?
I'm trying to return an observable on two promises via forkJoin.
One promise gets an ID from the server and another processes a file to generate a thumbnail.
export function createSceneFromFile(action$) {
return action$.ofType(CREATE_SCENE_FROM_FILE)
.mergeMap(({locationId,file}) =>
createSceneThumb(locationId,file)
.map((res,preview) => {
console.log(res,preview)
if (res.error) {
return { type: CREATE_SCENE_FAILED, payload: res.message }
} else {
return {type: CREATE_SCENE_SUCCESS, payload: {...res.payload,preview} }
}
})
.catch(err => { return { type: CREATE_SCENE_FAILED, message: err } })
)
}
function createSceneThumb(locationId,file){
const request = fetch(`${API_URL}/location/${locationId}/createscene/${file.name}/`, {
method: 'get',
credentials: 'include',
}).then(res => res.json())
const thumb = fileToScenePreview(file)
return Observable.forkJoin(request,thumb)
}
export function fileToScenePreview(file){
return new Promise((resolve,reject)=>{
getFileThumb(file).then((canvas)=> {
canvas.toBlob((blob) => {
blob.lastModifiedDate = new Date()
blob.name = 'scenepreview_' + file.name + '.png'
const uploader = new S3Upload({
getSignedUrl: getSignedUrl,
uploadRequestHeaders: {'x-amz-acl': 'public-read'},
contentType: 'image/png',
scrubFilename: (filename) => filename.replace(/[^\w\d_\-.]+/ig, ''),
contentDisposition: 'auto',
s3path: 'assets/',
onError:()=>reject,
onFinishS3Put: ()=>resolve(blob.name),
})
uploader.uploadFile(blob)
})
})
})
}
But i never get a response.
Is this the right way of going about it?

redux observable: Why don`t can get all actions in test

I'm trying to test a 'redux observable epic' but the test fail because not all actions are in store.getActions() the strange is the store.dispatch function runs.
Epic and actions
export const VERIFY_SESION = 'auth/VERIFY_SESION';
export const SET_POLICIES_ACCEPTED = 'auth/SET_POLICIES_ACCEPTED';
export const AUTHENTICATE = 'auth/AUTHENTICATE';
export function setPoliciesAccepted(wereAccepted: boolean) {
return {
wereAccepted,
type: SET_POLICIES_ACCEPTED,
};
}
export function verifySesion() {
return {
type: VERIFY_SESION,
};
}
export function authenticate(token) {
return {
token,
type: AUTHENTICATE,
};
}
export function verifySesionEpic(action$, store) {
return action$
.ofType(VERIFY_SESION)
.switchMap(async () => {
try {
store.dispatch(setBlockLoading(true));
const token = await AsyncStorage.getItem('token');
if (token !== null) {
store.dispatch(setBlockLoading(false));
return authenticate(token);
}
const policiesWereAccepted = await AsyncStorage.getItem('policiesWereAccepted');
store.dispatch(setBlockLoading(false));
return setPoliciesAccepted(policiesWereAccepted);
} catch (error) {
return setMessage(error.message);
}
});
}
test
describe('actions/auth', () => {
let store;
const asyncStorageGetStub = stub(AsyncStorage, 'getItem');
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
asyncStorageGetStub.restore();
});
it('Should call authenticate if token', () => {
const token = 'mitoken';
asyncStorageGetStub.withArgs('token').returns(Promise.resolve(token));
store.dispatch(verifySesion());
expect(store.getActions()).toContain({ type: AUTHENTICATE, token });
});
});
Test result
1) "actions/auth Should call epic for verifySesion:
Error: Expected [ { type: 'auth/VERIFY_SESION' } ] to include { token: 'mitoken', type: 'auth/AUTHENTICATE' }"
Note
im sure that the conditional token !== null pass
I was to add a timeout before getAction because the 'AUTHENTICATE' actions is added after.
it('Should call authenticate if token', (done) => {
const token = 'mitoken';
asyncStorageGetStub.withArgs('token').returns(Promise.resolve(token));
store.dispatch(verifySesion());
setTimeout(() => {
expect(store.getActions()).toContain({ type: AUTHENTICATE, token });
done();
}, 1000);
});

Resources