React/Redux chaining a second API request and dispatch - javascript

Still getting used to this.
I have checked out questions here and here and I still feel uncomfortable about how to go about this. These are not clear from my perspective.
Basically I have clicked on the "Login" button and have requested a JWT login token and succesfully received it. What I have noticed is that I can at this point download those rarely changed lists like states or suburbs/postcodes which are large (the latter) and should be at the spa's immediate use rather than tacking it onto a call for a client.
I want to, upon successfully receiving the token immediately do an API call for those lists and store them in local storage. There is no "event" it should fire straight after a successful retreival of a JWT...
Here is the action for retreiving a JWT in react:
export const requestLoginToken = (username, password) =>
(dispatch, getState) => {
dispatch({type: REQUEST_LOGIN_TOKEN, payload: username})
const payload = {
userName: username,
password: password,
}
const task = fetch('/api/jwt', {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
})
.then(handleErrors)
.then(response => response.json())
.then(data => {
dispatch({type: RECEIVE_LOGIN_TOKEN, payload: data})
saveJwt(data)
})
.catch(error => {
clearJwt()
dispatch({type: ERROR_LOGIN_TOKEN, payload: error.message})
})
addTask(task)
return task
}
I beleive the place for adding a second API call would be after "saveJwt()" but how.
Do/can I send it off to another export const action/function in another part of the application?
If I write something similar to this and by putting the name of the function in with a parameter of "JWT" eg
.then(retrieveSelectData)
that it will go off to that separate folder with that export function and execute an API call at the same time applying a dispatch...and then return..
Could some one outline if this is a correct and reasonable way of making two API calls as one. I still have to get the JWT (and use it in the second) so I cant do the second call without the first.

If i understand your goal correctly, what you need here is a middleWare that will catch all actions that dispatched before the reducers catches them and can accept functions and holds a ref to the dispatcher.
Note that reducers can only accept actions that are plain objects and can't accept functions.
Enters redux-thunk, a middleware that does just that (and more) in only 11 lines of code.
It catches all actions (before the reducers) and checks to see if this action === 'function'.
If it is a function then it will call that function with dispatch as the argument.
If it's not a function it will leave it alone and let the reducers do their job.
Something like this:
function loadSomeThings() {
return dispatch => {
fetchFirstThingAsync.then(data => { // first API call
dispatch({ type: 'FIRST_THING_SUCESS', data }); // you can dispatch this action if you want to let reducers take care of the first API call
return fetchSecondThingAsync(data), // another API call with the data received from the first call that returns a promise
})
.then(data => {
dispatch({ type: 'SECOND_THING_SUCESS', data }); // the reducers will handle this one as its the object they are waiting for
});
};
}

Related

onError Authentication with refresh token

In the Apollographql documentation it states:
The onError link can retry a failed operation based on the type of GraphQL error that's returned. For example, when using token-based authentication, you might want to automatically handle re-authentication when the token expires.
This is followed up by their sample code:
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
// Apollo Server sets code to UNAUTHENTICATED
// when an AuthenticationError is thrown in a resolver
case "UNAUTHENTICATED":
// Modify the operation context with a new token
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
// Retry the request, returning the new observable
return forward(operation);
}
}
}
// To retry on network errors, we recommend the RetryLink
// instead of the onError link. This just logs the error.
if (networkError) {
console.log(`[Network error]: ${networkError}`);
}
});
My question is in regards to the getNewToken(), as no code was provided for this function, I want to know (assuming this is another request to the backend and I am not sure how it could not be), if you are able to and or supposed to use query/mutation in graphql or make the request through axios for example.
One problem, if it can/should be a graphql query or mutation, is to get the new token, the onError code is defined in the same file as the ApolloClient as ApolloClient needs access to onError, thus when trying to implement this as retrieving a new token through a graphql mutation I got the following error:
React Hook "useApolloClient" is called in function "refresh" that is
neither a React function component nor a custom React Hook function.
After trying to useQuery/useMutation hook and realizing I cannot outside of a react component and at the top level I found this post whose answers suggested you can use useApolloClient.mutate instead but I still ran into issues. My code was (and tried multiple iterations of this same code like useApolloClient() outside of the function and inside etc.):
const refresh = () => {
const client = useApolloClient();
const refreshFunc = () => {
client
.mutate({ mutation: GET_NEW_TOKEN })
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
};
refreshFunc();
};
I could capitalize Refresh but this still would not work and would break the rules of hooks.
And to clarify all the above would do is I would replace the console.logs with setting session storage to the retrieved new token and then re trying the original request with onError.
Now in another post I found when looking into this, the users getNewToken request was a rest request using axios:
const getNewToken = async () => {
try {
const { data } = await axios.post(
"https://xxx/api/v2/refresh",
{ token: localStorage.getItem("refreshToken") }
);
localStorage.setItem("refreshToken", data.refresh_token);
return data.access_token;
} catch (error) {
console.log(error);
}
};
Now from my understanding, if I wanted to implement it this way I would have to change my backend to include express as I am only using apolloserver. Now I could definitely be wrong about that as my backend knowledge is quite limited and would love to be corrected their.
So my question is, what is the best way to do this, whether natively using graphql queries/mutations (if possible), doing it with axios, or maybe their is another best practice for this seemingly common task I am unaware of.

Adyen Drop-in - how to pass unique order ID?

I have been trying to use the Adyen Drop-in component to make payments on the Razor pages site I am developing. I have got a test version running that makes a payment for a hard-coded amount but I have yet to figure out how to pass a unique order ID to my API endpoint making the payment request.
Taking the examples from https://docs.adyen.com/online-payments/drop-in-web, the drop-in component is mounted via JavaScript using
const checkout = new AdyenCheckout(configuration);
const dropin = checkout.create('dropin').mount('#dropin-container');
where the configuration object is created with something like
const configuration = {
paymentMethodsResponse: paymentMethodsResponse, // The `/paymentMethods` response from the server.
clientKey: "YOUR_CLIENT_KEY", // Web Drop-in versions before 3.10.1 use originKey instead of clientKey.
locale: "en-US",
environment: "test",
onSubmit: (state, dropin) => {
// Your function calling your server to make the `/payments` request
makePayment(state.data)
.then(response => {
if (response.action) {
// Drop-in handles the action object from the /payments response
dropin.handleAction(response.action);
} else {
// Your function to show the final result to the shopper
showFinalResult(response);
}
})
.catch(error => {
throw Error(error);
});
},
onAdditionalDetails: (state, dropin) => {
// Your function calling your server to make a `/payments/details` request
makeDetailsCall(state.data)
.then(response => {
if (response.action) {
// Drop-in handles the action object from the /payments response
dropin.handleAction(response.action);
} else {
// Your function to show the final result to the shopper
showFinalResult(response);
}
})
.catch(error => {
throw Error(error);
});
}
};
Adyen's own JavaScript then supplies the state object for the onSubmit method, so that my API endpoint gets called with a PaymentRequest object created (somehow) from the state.data.
However, without being able to get a unique order ID into this PaymentRequest object, my server-side code does not know what amount to set. Note that one can set an Amount object in the configuration object but this is just used to display the value on the Drop-in component - the value is not passed to the server.
So how does one pass a unique order ID via the Drop-in component?
The Adyen docs don't explicitly provide an example here, but the makePayment() and makeDetailsCall() presume that you will take the state.data and post back to your server. You need to implement your own code here. At that point, you could add additional information like any identifiers.
Here is an example implementation as a reference:
async function makePayment(state_data) {
const order_id = ""; // You need to provide this however your client stores it.
const json_data = {
order_id,
state_data,
};
const res = await fetch("[url to your server's endpoint]", {
method: "POST",
body: JSON.stringify(json_data),
headers: {
"Content-Type": "application/json",
},
});
return await res.json();
}
Another helpful resource could be the Adyen node.js/express tutorial. It is more explicit on implementation details so might help remove some ambiguity.

Updating populated array in MongoDB - patch request is being accepted but not updating with new item

I am trying to update a populated array stored in a MongoDB database with a new item using Javascript & Node.js, and while the patch request is technically successful, it does not update with the ID of the new item, and checking the console, only the old IDs are passed through to the DB, not the newly created one. To be more specific, I am building out an "Add new question" feature for a quiz application.
I tried to utilize .depopulate(), thinking it was something related to .populate() that was causing the issue but received an error stating that it was not a function, and am now thinking that is not the issue. Is there something I am overlooking with my code? Various snippets are included below.
Route (which works in other functions that update other aspects of the quiz):
router.patch('/quizzes/:id', requireToken, removeBlanks, (req, res, next) => {
delete req.body.quiz.owner
Quiz.findById(req.params.id)
.then(handle404)
.then(quiz => {
requireOwnership(req, quiz)
return quiz.updateOne(req.body.quiz)
})
.then(() => res.sendStatus(204))
.catch(next)
})
How questions are stored in the quiz Mongoose model:
questions: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Question'
}]
Here, I am pulling out the existing objectIds, as my 'questions' Mongoose schema is type: mongoose.Schema.Types.ObjectId,
const reAddQuestionIds = () => {
const quiz = store.quizData.questions
quiz.forEach(function (arrayItem) {
store.questions.push(arrayItem._id)
})
}
Event function to create question & add the newly created question to the quiz:
const onAddQuestion = event => {
event.preventDefault()
const form = event.target
const formData = getFormFields(form)
// call above function
reAddQuestionIds()
api.addQuestion(formData)
// get the newly created question
.then(res => api.getOneQuestion(res.question._id)
// push the id of the new question to the array of existing questions
// this line seems to be the issue?
.then(res => store.questions.push(res.question._id)))
.then(store.quizData.numOfQuestions++)
// call ajax function to update api
.then(api.addQuestionToQuiz()
.then(console.log))
.catch(console.error)
}
API call:
const addQuestionToQuiz = () => {
console.log('store.questions: ', store.questions)
return $.ajax({
url: config.apiUrl + '/quizzes/' + store.quizData._id,
method: 'PATCH',
headers: {
Authorization: `Token token=${store.user.token}`
},
data: {
quiz: {
questions: store.questions,
numOfQuestions: store.quizData.numOfQuestions
}
}
})
}
Now, a unexpected thing that happens, is in the above console log: when I check what is currently in the stored array in Chrome devtools, it shows there being one less object than what should be in there (i.e. if there were two questions previously, then I added the new question, it still just shows two objects), but when I expand, the new ID is there and it shows it as the correct length. And again, in checking what is passed through to the backend route, only the previously-created IDs are passed through.
Update: error was in the API call to the event handler function. I removed adding the code incrementing the number of questions (moved this to the API call), and in my third .then(), made sure to take the response and pass it to the api.AddQuestionToQuiz() call.
Discovered by logging store.questions upon successful creation of the new question, and saw that all questions were there, whereas the console.log in the API call function was missing the newly created question.
api.addQuestion(formData)
.then(res => api.getOneQuestion(res.question._id)
.then(res => store.questions.push(res.question._id)))
.then(res => api.addQuestionToQuiz()
.then(console.log))
.catch(console.error)

Redirect/ Routing issues in React and Express

I am creating a flight search app that makes external api calls. The data will be reorganized and returned in search results component.
On submit, the data is sent to express and takes about 10 seconds or more to complete all the api calls.
I think I need a loader at some point for during the delay of api calls, but also I am unsure of how to send/render the data.
As it stands, I have two pages home.js- '/' where i make the search and is sent to the server side, and prices.js- '/search' which when loaded fetches the data from the json file. but i do not have them connected
Both files work but I need to connect them. When I press submit, the user inputs are sent to server and the api calls are made but in order to see the results i have to manually refresh localhost:3000/search.
In express app after all the api calls, I tried res.redirect method, however the error given was setting the headers after sent to the client.
In react, I tried after submitting, to redirect to the search page. However I could not get it to redirect and also as soon as the /search page is called, it fetches the data from the file. This will happen before the api has finished writing to file and therefore the previous search results will render.
--in app.js
setTimeout(() => {
Promise.all([promise, promise2]).then(values => {
return res.redirect('http://localhost:3000/search');
});
}, 25000);
I had to wrap the api calls in promises so it will only redirect after all is written to file.
(in react prices.js)
componentDidMount() {
fetch('/search')
.then(res => {
return res.json()
})
.then(res => {
console.log(res.toString());
this.setState({flightData: res});
})
.catch(error => console.log(error));
}
home.js
home.js
```
onChange = (e) => {
this.setState({
originOne: e.target.value, originTwo: e.target.value});
};
onSubmit = (e) => {
e.preventDefault();
const { originOne, originTwo ,redirectToResult} = this.state;
};
```
app.js - I have all the functions calling each other in a waterfall style ( probably not the best way I know)
app.post('/', function getOrigins(req,res) {
var OrigOne;
var OrigTwo;
....
function IataCodeSearchOrg1(res, OrigOne, OrigTwo) {
...
findPrices(res,A,B)
}
function findPrices(res, A, B) {
promise = new Promise(function (resolve) {
...
}
}
All the methods are called within eachother. The api calls are in a loop and after each iteration they are written to the json file.
All these functions are in the app.post method and i tried to res.redirect but it did not work.
EDIT:
You can't redirect server-side from an XHR request. You would need to redirect client-side.
e.g.
componentDidMount() {
fetch('/search')
.then(res => res.json())
...
.then(() => window.location = '/myredirecturl')
}

Where to make Ajax calls in React flux

I have a react js app where the user can create a user and then i make a http post to the backend.
i have a action that looks like this
export function createUser(name, username, password) {
dispatcher.dispatch({
type: "CREATE_USER",
name,
username,
password,
});
}
then in my store i call action and it triggers a function that makes a http post to the backend that looks like this
handleActions(action) {
switch(action.type) {
case "CREATE_USER": {
this.createUser(action.name, action.username, action.password);
break;
}
default:
}
}
should i make the ajax call in the store or in the action it self?
First of all you would want redux-thunk which give you opportunity to create actions which dispatches other actions in async way.
After this you can create an action which make a call to server, and on result dispatch new action which would bring to store new data. For example:
getData(param) {
return (dispatch) => {
dispatch(dataRequestAction());
return fetch(`/data/${param}`)
.then(r => r.json())
.then(data => dispatch(setDataAction(data)))
.catch(err => dispatch(errroDuringRataRetrieving(err)))
};
}
as you see here you have one action (getData) which actually doesn't change the store, but trigger 'dataRequestAction' which put in to store data that request started. Then if request completed one of action can be triggered:
setDataAction - if all ok;
errroDuringRataRetrieving - if request failed.
In this way you can handle ajax via redux.
I think we should have seperate folder called api. There we will have our all api calls. We can inject those file and call those function where we put our functions which call api and respond action.

Categories

Resources