Chaining synchronous Redux actions and consuming in component - javascript

I have two Redux actions which need to execute synchronously. requestStripeToken is called in my component (signupComponent.js), but in order to get a Stripe Token I first need to make a call to an internal API to get the current Stripe Key (as this changes depending on the environment and SKU). Both of these functions are set up as separate actions in my actions file (actions.js).
The issue I have it that I am not sure how to consume the requestStripeToken function in my component. I don't know whether it is an issue with what I am returning in the requestStripeToken action or whether the Promise consumption logic in my component needs to change. Note I am using redux-thunk middleware.
// actions.js
export function requestStripeToken(values) {
return function(dispatch) {
const { cardNumber, cvc, nameOnCard, expiryMonth, expiryYear, billingLine1, billingLine2, billingCity, billingState, billingZip, billingCountry } = values;
// We need to get the Stripe key before we can request a Stripe Token
return dispatch(getStripeSecretKey())
// Curried function necessary as getStripeSecretKey returns the fetch Promise inside of function(dispatch) ?
.then(() => (key) => {
console.log(key);
return new Promise((resolve, reject) => {
Stripe.setPublishableKey(key);
Stripe.card.createToken({
number: cardNumber,
cvc,
name: nameOnCard,
exp_month: expiryMonth,
exp_year: expiryYear,
address_line1: billingLine1,
address_line2: billingLine2,
address_city: billingCity,
address_state: billingState,
address_zip: billingZip,
address_country: billingCountry,
}, (status, response) => {
if (response.error) {
dispatch(addNotification({
message: response.error.message,
level: `error`,
autoDismiss: 0,
}));
reject();
}
return resolve(response.id);
});
});
});
};
}
export function getStripeSecretKey() {
return function(dispatch) {
return fetch(`${getAPIPath}api/stripeKey`, {
method: `GET`,
credentials: `include`,
headers: {
Accept: `application/json`,
},
})
.then(handleErrors)
.then((response) => {
response.json().then((res) => {
return res.data;
});
})
.catch(response => response.json().then((res) => {
dispatch(addNotification({
message: res.message,
level: `error`,
autoDismiss: 0,
}));
throw res;
}));
};
}
console.log(key) in this file never gets called.
// signupComponent.js
handleCreateAccountSubmit = (values) => {
this.setState({ submitting: true });
// We need the Stripe Token before we can signup the user so needs to be synchronous
this.props.actions.requestStripeToken(values)
.then((stripeToken) => {
console.log(stripeToken);
this.signupUser(values, stripeToken);
})
.catch(() => this.stopSubmission());
}
console.log(stripeToken) in this file returns:
ƒ (key) {
console.log(key);
return new Promise(function (resolve, reject) {
Stripe.setPublishableKey(key);
Stripe.card.createToken({
number: cardNumber,
…

You need to return Promises in your getStripeSecretKey() as well.
Dispatch returns what the action creator returns, so if you do:
export function getStripeSecretKey() {
return function(dispatch) {
return fetch(${getAPIPath}api/stripeKey, {
method:GET,
credentials:include,
headers: {
Accept:application/json,
},
})
.then(handleErrors) // also return Promise.reject() in errors
.then((response) => {
return response.json().then((res) => { // DONT BREAK RETURN CHAIN
return Promise.resolve(res.data); // RESOLVE
});
})
.catch(response => response.json().then((res) => {
dispatch(addNotification({
message: res.message,
level:error,
autoDismiss: 0,
}));
return Promise.reject(res); // REJECT
}));
};
}

Related

Variable Assignment of Nested Promise Not Resolving (Stripe API)

Using the Stripe API, I am calling all products stripe.products.list(), and then attempting to append pricing data stripe.prices.list() for each product using the map() function. Each call returns a promise, although the nested promise is not resolving, and is instead returning an empty prices object. What am I missing?
export function handler(event, context, callback) {
stripe.products
.list()
.then(products =>
products.data.map(product => ({
...product,
prices: stripe.prices // <-- ASSIGNMENT NOT RESOLVED
.list({ product: product.id })
.then(prices => {
console.log(prices) // <-- RESOLVED IN CONSOLE
return prices
}),
}))
)
.then(rsp => {
callback(null, {
headers: { 'Content-Type': 'application/json' },
statusCode: 200,
body: JSON.stringify(rsp),
})
})
.catch(err => console.warn(err))
}
You are not awaiting for the internal promise to resolve:
export function handler(event, context, callback) {
stripe.products
.list()
.then(products =>
return Promise.all(products.data.map(product => {
return stripe.prices // <-- ASSIGNMENT NOT RESOLVED
.list({ product: product.id })
.then(prices => {
console.log(prices) // <-- RESOLVED IN CONSOLE
return prices
}),
}))
.then(prices => {
return {
...product,
prices
};
});
)
.then(rsp => {
callback(null, {
headers: { 'Content-Type': 'application/json' },
statusCode: 200,
body: JSON.stringify(rsp),
})
})
.catch(err => console.warn(err))
}
Array.map is executed synchronous and you are not awaiting the promises within products.data.map. The prices object is not empty, it is a promise. The output in the console is generated only after the handler has already returned.
A simple way, would be the following. Ie store the received products in a variable. Then use Promise.all() to get the prices for all of the products. And then combine those two arrays for the final result.
export function handler(event, context, callback) {
let theproducts;
stripe.products
.list()
.then(products => {
theproducts = products.map(p => p.data);
return Promise.all(products.data.map(product =>
stripe.prices.list({ product: product.id }))
)
})
.then(prices => theproducts.map((p, i) => ({...p, prices: prices[i]})
.then(rsp => {
callback(null, {
headers: { 'Content-Type': 'application/json' },
statusCode: 200,
body: JSON.stringify(rsp),
})
})
.catch(err => console.warn(err))
}
You can also merge products and price immediately when stripe.prices.list() resolves and so spare an additional iteration of the map. But I personally find it more readable this way. And if you don't have too many products, the additonal iteration doesn't take too long.
With lots of helpful input, I ended up with the following.
export function handler(event, context, callback) {
stripe.products
.list()
.then(products => {
return Promise.all(
products.data.map(product =>
stripe.prices
.list({ product: product.id })
.then(prices => {
return prices
})
.then(prices => {
return {
...product,
prices,
}
})
)
)
})
.then(rsp => {
callback(null, {
headers: { 'Content-Type': 'application/json' },
statusCode: 200,
body: JSON.stringify(rsp),
})
})
.catch(err => console.warn(err))
}
You have returned the array within products.data.map. However this is an array of promises. When you map them and chain the promise. It does not actually resolve the promise. I suggest you try some async syntax to simplify the code.
(async () => {
const blah = products.data.map(product => {
const prices = await stripe.prices.list({ product: product.id });
return { ...product, prices };
});
console.log(blah);
})()

Test method is overspecified

I have this before function in my test:
before((done) => {
const cognito = new Cognito();
return cognito.authUser(
'john#doe.com',
'password',
)
.then((res) => {
AuthToken += res.AuthenticationResult.IdToken;
done();
})
.catch((err) => {
done(err);
});
});
It throws this error:
Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.
I thought this may have been the fix:
before((done) => {
const cognito = new Cognito();
return new Promise(function(resolve) {
cognito.authUser(
'john#doe.com',
'password',
)
})
.then((res) => {
AuthToken += res.AuthenticationResult.IdToken;
done();
})
.catch((err) => {
done(err);
});
});
but it gives me this error:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
How do I resolve this?
The error explains a little bit.
You can't use both callback and a return.
You have 2 options:
callback (the done parameter)
before((done) => {
const cognito = new Cognito();
cognito.authUser(
'john#doe.com',
'password',
)
.then((res) => {
AuthToken += res.AuthenticationResult.IdToken;
done();
})
.catch((err) => done(err));
});
or
Return promise
before(() => {
const cognito = new Cognito();
return cognito.authUser(
'john#doe.com',
'password',
)
.then((res) => {
AuthToken += res.AuthenticationResult.IdToken;
})
});

TypeError: undefined is not an object (evaluating '_this.props.auth(values.username, values.password).then')

I'm developing a ReactJS app.
I'm getting the following error "TypeError: undefined is not an object (evaluating '_this.props.auth(values.username, values.password).then')".
When the "return new Promise" is outside the "then" it works properly. Nonetheless, I want to return the promise after only the two first "then"s.
Sample of loginActions.js
export const auth = (username, password) => dispatch => {
fetch('http://localhost/webservices/login', {
method: 'post',
body: JSON.stringify({ username, password })
})
.then(res => {
if(res.ok) {
console.log("Succeeded.", res);
return res.json();
} else {
console.log("Failed.", res);
return res.json();
}
})
.then(json => {
if (json.token) {
auth_status.value = true;
return auth_status.value;
} else {
auth_status.value = false;
return auth_status.value;
}
})
.then(function(res){
return new Promise((resolve, reject) => {
dispatch({
type: VERIFY_AUTH,
payload: res
});
resolve();
})
})
.catch(err => {
console.error(err);
});
};
Sample of login.js
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log("Received values of form: ", values);
this.props.auth(values.username, values.password).then(() => {
if (this.props.auth_status === true) {
message.success("Welcome!", 3);
this.setState({
redirect: true
});
} else {
message.error("The username and password combination is incorrect", 3);
}
})
.catch(err => {
console.error(err);
});
}
});
};
Your action auth is not returning anything. The return statements in the asynchronous handlers do not return for the action itself.
You need to return a Promise in your auth() action that you resolve yourself in the third then:
export const auth = (username, password) => dispatch => {
// instantly return a new promise that
// can be resolved/rejected in one of the handlers
return new Promise((resolve, reject) => {
fetch('http://localhost/webservices/login', {
method: 'post',
body: JSON.stringify({
username,
password
})
}).then(res => {
if (res.ok) return res.json();
// your probably also want to reject here
// to handle the failing of the action
reject();
}).then(json => {
if (json.token) {
auth_status.value = true;
return auth_status.value;
} else {
auth_status.value = false;
return auth_status.value;
}
}).then(res => {
dispatch({
type: VERIFY_AUTH,
payload: res
});
// resolve the original promise here
resolve();
}).catch(err => console.error(err));
});
};

Redux return promise

I have a simple function to log out (just for testing) and I would like to inform the user when the action is completed. First that came in mind is to do this with promises.
I tried like this but there is something wrong with it. I don't quite understand how these works. Am I able to do it like this or would there be a better approach?
Function
logOut = () => {
this.props.logoutUser().then((passed) => {
if (passed) {
alert("You are now logged out!");
}
});
};
Logout action
export function logoutUser() {
return dispatch => {
new Promise(function (resolve, reject) {
dispatch(logout()).then((response) => {
return true;
}).catch((error) => {
return false;
});
});
}
}
function logout() {
return {
type: "LOGOUT"
}
}
Problem with Logout function
export function logoutUser() {
return dispatch => {
new Promise(function (resolve, reject) {
dispatch(logout()).then((response) => {
resolve(true); // changed
}).catch((error) => {
reject(error); // changed
});
});
}
}
you have to pass callback function resolve for success, and reject for fail.
refer this link
Update : and secondly you have to use thunk middleware, to work dispatch like Promise object : github
you can also do this with callback, like so -
logOut = () => {
this.props.logoutUser(result => {
if (result.success) {
alert("You are now logged out!");
return;
}
// Handle error
});
};
export function logoutUser(callback) {
logout()
.then(() => callback({ success: true }))
.catch(error => callback({ error }));
return dispatch => {
dispatch({
type: "LOGOUT"
});
};
}
function logOut() {
// log out function that returns a promise
}

Async and localStorage not working properly

So I'm using React with React-Router.
I have a onEnter hook which checks if the user is authenticates yes/no and executes the desired action.
export function requireAuth(nextState, replaceState) {
if (!isAuthenticated()) {
if (!Storage.get('token')) replaceState(null, '/login');
return delegate().then(() => replaceState(null, nextState.location.pathname));
}
if (nextState.location.pathname !== nextState.location.pathname) {
return replaceState(null, nextState.location.pathname);
}
}
When the token is expired I call a delegate function which looks like:
export function delegate() {
const refreshToken = Storage.getJSON('token', 'refresh_token');
return getData(endpoint)
.then(res => {
Storage.set('token', JSON.stringify({
access_token: res.data.access_token,
refresh_token: refreshToken,
}));
});
}
The delegate function indeed refresh the tokens in the localStorage. But the requests after the replaceState are not using the updated token, but the previous one. I think this is a async issue, someone knows more about this?
Edit: The function where I use the token:
function callApi(method, endpoint, data) {
return new Promise((resolve, reject) => {
let headers = {
'Accept': 'application/json',
'X-API-Token': Storage.getJSON('token', 'access_token'),
};
const body = stringifyIfNeeded(data);
const options = { method, headers, body };
return fetch(endpoint, options)
.then(response =>
response.json().then(json => ({ json, response }))
).then(({ json, response }) => {
if (!response.ok) {
reject({ json, response });
}
resolve(json);
}).catch((error, response) => {
reject({ error, response });
});
});
}

Categories

Resources