Should action talk with store? - javascript

I am using react with flux architecture and I have a problem I face it.
I need to create an action that gets a user id and fetches the user. Here is the code:
var createAction = require('common/scripts/actions-helpers/create-action'),
resource = require('common/scripts/resources/conversation');
module.exports = createAction(fetchAction);
function fetchAction(context, payload, success, failure) {
resource.sync(context, payload.userId)
.then(function(user) {
context.dispatch('USER_FETCH', user);
success();
}, failure);
}
I want to use a store that will cache all users so in case the user fetched before, the action will not perform a backend call. The new action should look like that:
function getFetchedUser() {
// <--------- HOW TO KNOW WHETHER USER FETCHED?
}
function fetchAction(context, payload, success, failure) {
var user = getFetchedUser();
if (user) {
context.dispatch('USER_FETCH', user);
success();
} else {
resource.sync(context, payload.userId)
.then(function(user) {
context.dispatch('USER_FETCH', user);
success();
}, failure);
}
}
The issue is that I don't want to manage users data in the action so the only way come in my mind to implement getFetchedUser() is checking in the Users store.
Is this a good approach?
Can action access to store?

The common approach is to do the backend call from the action and then dispatch an action when the request either succeeds or fails. This way you can populate your store with an action. This means that the store keeps synchronous and can just emit changes as they come along.
var AppDispatcher = require(<Flux AppDispatcher>);
var UserActions = {
fetchUser: function(payload) {
fetchUserFromBackendApi(payload)
.then(function (error, user) {
if(error) {
AppDispatcher.dispatch({
actionType: "ERROR_CODE",
error: error
});
return;
}
AppDispatcher.dispatch({
actionType: "SUCCESSFUL_USER",
user: user
});
}
}
};
module.exports = UserActions;
Then you just handle different action types in the store and just populate your cache. When the cache is populated you emit a change event and the data will be rendered in.
This code ain't the prettiest so howl out if I misunderstood or if there are any questions.
So to answer the intial answer. The action component should not talk to the store. It goes against one of the key concepts of React, loose coupling.
With Flux and React the data flow works out because the components will fire actions when they need data. The actions just fetch the data.

I also had a similar problem. I have a PhotosStore that keep all photos from a user, server send it paginated.
User can reorder photos by more recently, more commented, more viewed. Is PhotosStore has all page loaded, it can reorder by itself, if not, need call server.
# actions
Actions.Photo = createActions(
'sortBy', {'load': asyncResult: true}
)
# store
Store.Photos = Reflux.createStore(
listenables: Actions.Photo
onSortBy: (order) ->
if #hasMorePages()
Actions.Photo.load({order: order})
else
# reorder and trigger
onLoadCompleted: (data) ->
# get data and trigger
)
# view
PhotoHeaderOrder = React.createClass
mixins: [
Reflux.connect(Store.Photos, 'model')
]
onSortNew: -> Actions.Photo.sortBy('news')
onSortCom: -> Actions.Photo.sortBy('comments')
onSortVie: -> Actions.Photo.sortBy('views')
render: ->
# render code

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.

Call an api within .subcribe next in Angular

Essentially I am trying to edit a user, and calling the editUser method in my authService. Edit user in the auth service is just a HTTP post request.
this._authService.editUser(this.edit).subscribe(res=>{this.getUsers(this.params)}, (err)=>{alert(err)})
Now after editing the user, I wanna refresh the entire User list again. therefore I am calling the getUser method inside the .subscribe next field. The getUser method in the authservice is also a HTTP POST request.
getUsers(search){
this._authService.getUser(search).subscribe(
res => {
this.persons = res
},
error => {
if(error.status == 0) {
alert("The servers are down right now, try again later")
}
else{
alert(error.error.msg)
}
}
);
}
The problem with this is that it doesnt always refresh the persons list. Sometimes it does and sometimes it doesnt

Flow for authentication when MFA required for user in AWS Cognito

I am attempting to add MFA for user authentication to an already existing solution (built in Angular) for device management within AWS Cognito.
I am having trouble figuring out how to handle this particular response well from a user-experience perspective. It actually feels broken, so would love if anyone else has experience pain points here.
See Use Case 23. for example implementation, mine is below:
authenticate(username: string, password: string): Observable<any> {
// init cognitoUser here
return new Observable((observer) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result: any) => {},
onFailure: (err: Error) => {},
mfaRequired: (codeDeliveryDetails: any) => {
// SMS has just been sent automatically
// and it needs to be confirmed within this scope
// The example linked requests the code via `confirm()`
// which is awful UX...and since this is a service
// probably non-compliant with best practice
// However, without this `confirm` at this point in
// time, we have no confirmationCode below
cognitoUser.sendMFACode(confirmationCode, {
onSuccess: (result) => {
observer.next(result);
observer.complete();
}, onFailure: (err: Error) => {
observer.error(err);
observer.complete();
}
});
}
});
});
}
Expected:
If the user authenticates successfully but has not added this device through MFA, we can manage the redirect to appropriate confirmation code form page and trigger the sendMFACode function manually (perhaps through some sort of limited session?)
Issue/s:
we don't have a session, so we have no way of asking the user the MFA code sent automatically outside of this login screen...catch 22?
adding another show/hide field in the login form doesn't work as it would hit the sendMfaCode function multiple times, resulting in multiple SMS codes sent.
Has anyone had any luck stepping out of this flow?
Whilst I’m sure very talented people worked on the amazon-cognito-identity-js API, it is just straight up badly designed. Thus why it’s been depricated. My personal advise would be to migrate to Amplify, which makes me much less angry.
With Amplify you can do these ones.

import Amplify from 'aws-amplify'
import Auth from '#aws-amplify/auth'
let mfaRequired = false
Amplify.configure({
Auth: {
userPoolWebClientId: '',
userPoolId: ''
}
})
const logUserIn = (user) => {
// Go forth and be happy
}
// Run me on your login form's submit event
const login = async (username, password) => {
const user = await Auth.signIn(username, password)
if (user.challengeName === 'SMS_MFA') {
// Change UI to show MFA Code input
mfaRequired = true
return
}
return logUserIn(user)
}
// Run me when the user submits theire MFA code
const senfMfaCode = async (mfaCode) => {
const user = await Auth.confirmSignIn(mfaCode)
return logUserIn(user)
}
BUT if for some sad reason you need to keep using amazon-cognito-identity-js don’t worry. I got you.
Just keep the cognitoUser object stored outside the callback. The documentation is a little misleading because it only show’s self contained examples but there’s no reason that you can’t notify your UI when MFA is required and then call cognitoUser.sendMFACode() later.
Just remember that the documentation show’s the passing of this to sendMFACode() for scoping (which is terrible) but you can just declare your callbacks as a variable and share it between your authenticateUser() and sendMFACode() functions (or as many functions as you like).
import { CognitoUserPool, AuthenticationDetails, CognitoUser } from 'amazon-cognito-identity-js'
export let mfaRequired = false
export let cognitoUser = null
export const cognitoCallbacks = {
mfaRequired () {
// Implement you functionality to show UI for MFA form
mfaRequired = true
},
onSuccess (response) {
// Dance for joy the code gods be glorious.
},
onFailure () {
// Cry.
}
}
export const logUserIn = payload => {
cognitoUser = new CognitoUser({
Username: 'Matt Damon',
Pool: new CognitoUserPool({
UserPoolId: '',
ClientId: ''
})
})
return cognitoUser.authenticateUser(new AuthenticationDetails(payload), cognitoCallbacks)
}
export const sendMfaCode = MFACode => {
cognitoUser.sendMFACode(MFACode, cognitoCallbacks)
}
That’s a super basic implementation and on top of that you could,
Just overwrite the mfaRequired function in an external module to do whatever you want.
Wrap the whole thing in a pub/sub plugin and subscribe to events.
Hope that helps!
I know this is an old question, but I thought this answer might be helpful for anyone who is still using the amazon-cognito-identity-js API instead of Amplify. #stwilz's answer works somewhat, but there are a few complications that come when you stray too far away from the documentation's use cases (and might come about when doing TOTP MFA instead of SMS MFA). I've created a workaround to address situations where you might get errors like Invalid Access Token, Missing parameter Session, or Invalid session for the user.
If you need to use something like sendMFACodeoutside of the callbacks, it's not enough to just keep cognitoUser stored outside the callback. You actually have to call the authenticateUser function again, then call the sendMFACode within the callback. It gets more complicated with verifySoftwareToken for TOTP, where you actually have to store the Cognito user object and then reassign it when calling authenticateUser again.
If none of this makes sense, I've created a simple Github Gist that uses React and amazon-cognito-identity-js to show how such a flow would work. It's here: https://gist.github.com/harve27/807597824720d0919476c0262e30f587

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.

Rx.js and application workflow

I've got a web application where I'm using the Rx.js for handling event streams. The app uses a rest api provided by a backend.
Mostly I make a subscription for an api call and when a request is done I render results and reset other controls states (hide progress elements and so on).
Some api calls can be failed when an auth token is expired and I have to make a user to login again (show a login popup or so).
I'm curious is there a way to "restore" an api call stream after a successful login? Where a user has not to provide additional actions to get a server response.
Primitive example of the current workflow:
var apiCallStream = $.ajaxAsObservable(params):
apiCallStream.subscribe(
result => renderResult(result),
err => handleError(err));
function handleError(err) {
if (err.xhr.error === 401) {
LoginPopup();
} else {
ErrorPopup(err);
}
}
Here some some (very rough pseudo-code), but you could do this with retryWhen:
// use Rx.DOM.get for an Observable Ajax GET
var source = Rx.DOM.get('/some/end/point')
.retryWhen(function(errors) {
// retryWhen: errors is a stream of errors
// whatever observable you return, when it emits, the
// observable you're operating on will be retried. (the entire thing)
return errors.filter(function(e) { return e.status === 401; })
// if status is 401, then tell the user to login
.flatMapLatest(function() { return doUserLogin; });
});
// a contrived observable that shows a form and
// return a stream of successful logins
var doUserLogin = Observable.create(function(observer) {
// show a form
var form = $('.my-form').show();
// when submit is fired on said form...
return Rx.Observable.fromEvent(form, 'submit')
// send a login over ajax
.flatMap(e => Rx.DOM.post('some/url/login', form.serialize()))
});
Hopefully that gives you something to start with.

Categories

Resources