I want to show loader when authentication happens. I've got sendAuthCredentials action. On this action I want to do several actions:
Show Loader
Send auth request to server
Handle response and inform UserStore is user authenticated or not
Hide Loader.
I've got Loader react component, LoaderStore and LoaderAction for working with component.
So my sendAuthCredentials method looks like:
UserActions = {
/**
* #param {string} username
* #param {string} password
*/
sendAuthCredentials: function(username, password) {
return Q.all(
[
LoaderActions.showLoader(),
authenticateUser(username, password)
.then(function( data ) {
if( data ) {
AppDispatcher.handleViewAction({
type: ActionTypes.USER_LOGIN_SUCCESS,
data: data
});
return Q( "succcess" );
} else {
AppDispatcher.handleViewAction({
type: ActionTypes.USER_LOGIN_FAIL
});
return Q( "failed" );
}
})
]
).then(LoaderActions.hideLoader);
}
};
It's works but I'm not sure Is it right way to use Actions in Flux.
Thanks for the answer.
Your solution is fine as it is, but as you mention you are not utilizing the potential in Flux. If you utilize Flux you do not need to use promises to keep track of your async calls. The numbered list of actions you mention is just a regular Flux data flow.
With Flux you would have a UserStore where you could store if this user was authenticated or not (plus other data you would want to store about a user). You could then just prompt the loader until you got an event dispatched from the UserStore telling you that the user is authenticated or not.
var authenticateUser = function(username) {
<getRequestToAuthentticationResource>
.then(function(data){
<storeDataInUserStore>
emitAuthenticated();
})
}
var UserStore = _.extend({}, EventEmitter.prototype, {
dispatcherIndex: AppDispatcher.register(function(payload) {
var action = payload;
switch (action.actionType) {
case UserConstants.AUTHENTICATE:
authenticateUser(action.username);
break;
}
}
Under is an example of how your app would listen and handle the event. Just to clarify, this is a very simple component. You could check the authCode in a better way than with an if-else.
var YourApp = React.createClass({
getInitialState: function() {
return {
authCode: "loading"
}
},
componentDidMount: function() {
UserStore.addAuthListener(this._onChange);
},
_onChange: function() {
setState({
authCode: UserStore.getAuthCode() //Sucess or fail
});
}
render: function() {
if(this.state.authCode === "loading"){
return <load information>
} else if(this.state.authCode === "success") {
return <success information>
} else {
return <fail>
}
}
Then your React-component would just have to see what data it would get and show either the user information, error or the loader.
Just remember you need an addAuthListener in your store and an emitAuthenticated to make this work.
Best practice is to do the getRequest where you dispatch the actions, and when that returns you notify the store. However, as seen in the example, you can do it in the store as well.
Related
I'm trying to make a Post request on component Mount. But if user reloads the page or states changes, then the function is called again as I'm useEffect and it sends the request again. But I want any better thing where the Post request should be made once and if even the page refreshes the shouldn't be called again if it has been called.
I'm using the Function base component. and make Post requests using redux.
const Main = () => {
// ....
// Here I'm forcing user to login if there's user is logged in then want to make a silent post request, But it sends request everytime on state change.
useEffect(() => {
getLocalStorage()
if (!userInfo) {
setModalShow(true)
}
if (userInfo) {
dispatch(postRequest())
setModalShow(false)
}
}, [userInfo])
return (
<div>Some JSX </div>
)
}
export default Main
So need your help to fix that issue. Can we use localStorage to store the information either the post request is already have been made or any other better idea?
Best way is to use localstorage, not sure if my placements of setting ang getting value from localstorage are on the right spot.
const Main = () => {
// ....
// Here I'm forcing user to login if there's user is logged in then want to make a silent post request, But it sends request everytime on state change.
useEffect(() => {
getLocalStorage()
// Check if the value of logged is true initiali will be false until the
// first request if made
if (!!localStorage.getItem('logged')) {
setModalShow(true)
}
if (userInfo) {
dispatch(postRequest())
setModalShow(false)
// set the value when the request is finished
localStorage.setItem('logged', true)
}
}, [userInfo])
return (
<div>Some JSX </div>
)
}
export default Main
There is a package named redux-persist that you can save the state, for example in localStorage. You can use this package, and send post request if there is not any data in state.
Using localStorage for that purpose is pretty useful, you can save the information on post request whether it was made or not.
For a basic setup;
this could be like that:
const postRequestStatus = localStorage.getItem('postRequestMade') ? JSON.parse(localStorage.getItem('postRequestMade')) : null
useEffect(() => {
getLocalStorage()
if (!userInfo) {
setModalShow(true)
}
if (userInfo) {
setModalShow(false)
if (!postRequestStatus) {
dispatch(postRequest())
console.log('Post Request Made')
localStorage.setItem('postRequestMade', true)
}
}
}, [userInfo, postRequestStatus])
Here's a catch. As far there is information in localStorage, of postRequestMade true . The request won't be made. So some point on the site you should set any logic to clear it out where it is necessary.
Secondly, What if the request was not successful if there was an error from the server. Then, you should also consider error handling as well. As you mentioned you are using redux and I'm sure there would be Axios as well try the functionality like that:
useEffect(() => {
getLocalStorage()
if (!userInfo) {
setModalShow(true)
}
if (userInfo) {
setModalShow(false)
if (!postRequestStatus) {
dispatch(postRequest())
// That block will take care if request was successful
// After a successful request postRequestMade should be set to true.
if (success) {
console.log('Successful Request')
localStorage.setItem('postRequestMade', true)
}
}
}
}, [userInfo, postRequestStatus, success])
I have an app built using Ember and ember-apollo-client.
// templates/collaborators.hbs
// opens an ember-bootstrap modal
{{#bs-button type="success" onClick=(action (mut createCollaborator) true)}}Create collaborator{{/bs-button}}
// submit button in modal triggers "createCollaborator" in controller
{{#each model.collaborators as |collaborator|}}
{{collaborator.firstName}} {{collaborator.lastName}}
{{/each}}
// routes/collaborators.js
import Route from '#ember/routing/route';
import { RouteQueryManager } from 'ember-apollo-client';
import query from '../gql/collaborators/queries/listing';
export default Route.extend(RouteQueryManager, {
model() {
return this.get('apollo').watchQuery({ query });
}
});
// controllers/collaborator.js
export default Controller.extend({
apollo: service(),
actions: {
createCollaborator() {
let variables = {
firstName: this.firstName,
lastName: this.lastName,
hireDate: this.hireDate
}
return this.get('apollo').mutate({ mutation, variables }, 'createCollaborator')
.then(() => {
this.set('firstName', '');
this.set('lastName', '');
this.set('hireDate', '');
});
}
}
});
Currently, after creating a collaborator the data is stale and needs a browser refresh in order to update. I'd like the changes to be visible on the collaborators list right away.
From what I understood, in order to use GraphQL with Ember, I should use either Ember Data with ember-graphql-adapter OR just ember-apollo-client. I went on with apollo because of its better documentation.
I dont think I quite understood how to do that. Should I somehow use the store combined with watchQuery from apollo? Or is it something else?
LATER EDIT
Adi almost nailed it.
mutationResult actually needs to be the mutation itself.
second param in store.writeQuery should be either data: { cachedData } or data as below.
Leaving this here as it might help others.
return this.get('apollo').mutate({
mutation: createCollaborator,
variables,
update: (store, { data: { createCollaborator } }) => {
const data = store.readQuery({ query })
data.collaborators.push(createCollaborator);
store.writeQuery({ query, data });
}
}, createCollaborator');
You can use the apollo imperative store API similar to this:
return this.get('apollo').mutate(
{
mutation,
variables,
update: (store, { data: {mutationResult} }) => {
const cachedData = store.readyQuery({query: allCollaborators})
const newCollaborator = mutationResult; //this is the result of your mutation
store.writeQuery({query: allCollaborators, cachedData.push(newCollaborator)})
}
}, 'createCollaborator')
I'm using a very basic CASL implementation. Unfortunately, the docs aren't that detailed. I have the following code (basically copy-pasted from the docs).
import { abilitiesPlugin } from '#casl/vue'
import defineAbilitiesFor from './ability'
const ability = defineAbilitiesFor({name: 'guest'})
Vue.use(abilitiesPlugin, ability )
where defineAbilitiesFor is defined as (in ./ability.js)
import { AbilityBuilder } from '#casl/ability'
function defineAbilitiesFor(user) {
return AbilityBuilder.define((can, cannot) => {
can(['read'], 'foo', { username: user.name})
})
}
I know it's possible to update the rules/conditions (i.e. ability.update([])). But how do I update the user's information after initializing CASL? (e.g. after the user has logged in
CASL has nothing to do with user. What eventually it cares is only user's permissions. So, after login you need to update rules, basically use ability.update(myRules)
In your Login component, after login request to API (or after you receive information about currently logged in user), you need to call ability.update(defineRulesFor(user)).
ability can be just an empty Ability instance. For example:
const ability = new Ability([])
function defineRulesFor(user) {
const { can, rules } = AbilityBuilder.extract()
can(['read'], 'foo', { username: user.name })
return rules
}
// Later after login request to API (or after you receive information about currently logged in user)
login() {
return http.post('/login')
.then((response) => {
ability.update(defineRulesFor(response.user))
// after that Ability instance contains rules for returned user
})
}
I am trying to implement a search function where a user can return other users by passing a username through a component. I followed the ember guides and have the following code to do so in my routes file:
import Ember from 'ember';
export default Ember.Route.extend({
flashMessages: Ember.inject.service(),
actions: {
searchAccount (params) {
// let accounts = this.get('store').peekAll('account');
// let account = accounts.filterBy('user_name', params.userName);
// console.log(account);
this.get('store').peekAll('account')
.then((accounts) => {
return accounts.filterBy('user_name', params.userName);
})
.then((account) => {
console.log(account);
this.get('flashMessages')
.success('account retrieved');
})
.catch(() => {
this.get('flashMessages')
.danger('There was a problem. Please try again.');
});
}
}
});
This code, however, throws me the following error:
"You cannot pass '[object Object]' as id to the store's find method"
I think that this implementation of the .find method is no longer valid, and I need to go about returning the object in a different manner. How would I go about doing this?
You can't do .then for filterBy.
You can't do .then for peekAll. because both will not return the Promise.
Calling asynchronous code and inside the searchAccount and returning the result doesn't make much sense here. since searchAccount will return quickly before completion of async code.
this.get('store').findAll('account',{reload:true}).then((accounts) =>{
if(accounts.findBy('user_name', params.userName)){
// show exists message
} else {
//show does not exist message
}
});
the above code will contact the server, and get all the result and then do findBy for the filtering. so filtering is done in client side. instead of this you can do query,
this.store.query('account', { filter: { user_name: params.userName } }).then(accounts =>{
//you can check with length accounts.length>0
//or you accounts.get('firstObject').get('user_name') === params.userName
//show success message appropriately.
});
DS.Store#find is not a valid method in modern versions of Ember Data. If the users are already in the store, you can peek and filter them:
this.store.peekAll('account').filterBy('user_name', params.userName);
Otherwise, you'll need to use the same approach you used in your earlier question, and query them (assuming your backend supports filtering):
this.store.query('account', { filter: { user_name: params.userName } });
I'm stuck trying to figure out how to write a flux store and action that works in just fetching data from my express API using altjs
import $ from 'jquery';
const utils = {
myProfile: () => {
return $.ajax({
url: '/myProfile',
type: 'GET'
});
}
};
This is how I believe I should write my GET request for just grabbing a user's profile (which should return a json with user info).
then for my store :
import UserActions from 'actions/UserActions';
import alt from 'altInstance';
class UserStore {
constructor() {
this.userProfile = [];
this.on('init', this.bootstrap);
this.on('bootstrap', this.bootstrap);
this.bindListeners({
fetchUserProfile: UserActions.FETCHUSERPROFILE,
});
}
fetchUserProfile(profile) {
this.userProfile = profile;
}
}
export default alt.createStore(UserStore, 'UserStore');
However the action is where i'm the most clueless
import alt from 'altInstance';
import UserWebAPIUtils from 'utils/UserWebAPIUtils';
fetchProfile(){
this.dispatch();
UserWebAPIUtils.getProfile()
//what do we do with it to let our store know we have the data?
});
}
}
}
All im trying to do, is grab data from the server, tell my store we've recieved the data and fill the userprofile array with the data from our api, and the messenger for telling our store is through a dispatcher which belongs to 'actions' correct? I've looked at a lot of tutorials but I still dont feel very confident on how I am thinking about this. What if I wanted to update data through a POST request what would that be like?
Looking through altjs doc it seems like they recommend doing the async operations from actions. I prefer this approach as well because it keeps stores synchronous and easy to understand. Based on their example
LocationAction
LocationsFetcher.fetch()
.then((locations) => {
// we can access other actions within our action through `this.actions`
this.actions.updateLocations(locations);
})
.catch((errorMessage) => {
this.actions.locationsFailed(errorMessage);
});
Basically they are fetching the information and then triggering 2 actions depending on the result of the request which the store is listening on to.
LocationStore
this.bindListeners({
handleUpdateLocations: LocationActions.UPDATE_LOCATIONS,
handleFetchLocations: LocationActions.FETCH_LOCATIONS,
handleLocationsFailed: LocationActions.LOCATIONS_FAILED
});
When the store receives a handleUpdateLocations action which happens when the fetcher returns successfully. The store will update itself with new data and dispatch
handleUpdateLocations(locations) {
this.locations = locations;
this.errorMessage = null;
}
With your code you can do something similar. The fetch user profile will be triggered when data is originally requested. Here I am setting user profile to be [] which is your original init value but you can set it to anything to indicate data is being loaded. I then added 2 more methods, handleFetchUserProfileComplete and handleFetchUserProfileError which get called depending on if your fetch was successful or not. The code below is a rough idea of what you should have.
constructor() {
this.userProfile = [];
this.on('init', this.bootstrap);
this.on('bootstrap', this.bootstrap);
this.bindListeners({
handleFetchUserProfile: UserActions.FETCH_USER_PROFILE,
handleFetchUserProfileComplete: UserActions.FETCH_USER_PROFILE_COMPLETE,
handleFetchUserProfileError: UserActions.FETCH_USER_PROFILE_ERROR,
});
}
fetchUserProfile() {
this.userProfile = [];
}
handleFetchUserProfileComplete(profile) {
this.userProfile = profile;
}
handleFetchUserProfileError(error) {
this.error= error;
}
export default alt.createStore(UserStore, 'UserStore');
The only thing left is to trigger these 2 actions depending on the result of your fetch request in your action code
fetchUserProfile(){
this.dispatch();
UserWebAPIUtils.getProfile().then((data) => {
//what do we do with it to let our store know we have the data?
this.actions.fetchUserProfileComplete(data)
})
.catch((errorMessage) => {
this.actions.locationsFailed(errorMessage);
});
}
fetchUserProfileComplete(profile) {
this.dispatch(profile);
}
fetchUserProfileError(error) {
this.dispatch(error);
}