Creating a altjs flux store for fetching data from API - javascript

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);
}

Related

Vue prefetch data from separate backend

I have some queries from an API-Server that returns a json object that will be static over a user session, but not static forever.
It's a one-pager with Vue router.
How can I achieve that I:
can access this.myGlobals (or similar eg window.myGlobals) in all components, where my prefetched json-data from API-Server is stored.
My approach that is already working is to embed help.js via a mixin.
Oddly enough, I get hundreds of calls to this query. At first I thought that it only happened in the frontend and is chached, but the requests are actually sent hundreds of times to the server. I think it is a mistake of my thinking, or a systematic mistake.
i think the problem is, that the helper.js is not static living on the vue instance
main.js:
import helpers from './helpers'
Vue.mixin(helpers)
helpers.js:
export default {
data: function () {
return {
globals: {},
}
}, methods: {
//some global helper funktions
},
}, mounted() {
let url1 = window.datahost + "/myDataToStore"
this.$http.get(url1).then(response => {
console.log("call")
this.globals.myData = response.data
});
}
}
log in console:
call
SomeOtherStuff
(31) call
SomeOtherStuff
(2) call
....
log on server:
call
call
call (pew pew)
My next idea would be to learn vuex, but since its a easy problem, im not sure if i really need that bomb ?
You can use plugin to achieve this.
// my-plugin.js
export default {
install (Vue, options) {
// start fetching data right after install
let url1 = window.datahost + "/myDataToStore"
let myData
Vue.$http.get(url1).then(response => {
console.log("call")
myData = response.data
})
// inject via global mixin
Vue.mixin({
computed: {
myData () {
return myData
}
}
})
// or inject via instance property
Vue.prototype.$myData = myData
// or if you want to wait until myData is available
Vue.prototype.$myData = Vue.$http.get(url1)
.then(response => {
console.log("call")
myData = response.data
})
}
}
and use it:
Vue.use(VueResource)
Vue.use(myPlugin)

How to hit/consume post and get api in React Native with Ignite Bowser 2 Boilerplate. (Mobx state stree, type script)

I am new to React Native, please provide some Github link or your own code for reference. Consider me as a beginner in RN.
I found very less open support for RN, Mobx State tree, Ignite and all, so not just post and get API reference, if you find anything helpful related to these above-mentioned topics, Feel free to share.
Thanks in advance.
Mobx State Tree, With Ignite Bowler you would have api.ts file where you can specify API calls.
async getUser(userToken: string): Promise<Types.GetUserResult> {
// make the api call
const response: ApiResponse<any> = await this.apisauce.post(`api/v1/sales/login?authCode=${userToken}`)
if (!response.ok) {
const problem = getGeneralApiProblem(response)
if (problem) return problem
}
// transform the data into the format we are expecting
try {
try {
const rawUser = response.data
console.log('rawUser'+ rawUser)
const user: UserSnapshot = convertRawUserToUserStore(rawUser)
return { kind: "ok", user }
console.log({ user })
} catch (e) {
__DEV__ && console.tron.log(e.message)
return { kind: "bad-data" }
}
} catch {
return { kind: "bad-data" }
}
}
Consider, we will be getting user data from this API call,
you can notice that there is UserSnapshot which belongs to User Model, Snapshot will save the data automatically, you don't need Aysnc storage to save or retrieve data.

How to keep data synchronized in ember using ember-apollo-client?

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')

Adding a loading animation when loading in ReactJS

I would like to add a loading animation to my website since it's loading quite a bit when entering the website. It is built in ReactJS & NodeJS, so I need to know specifically with ReactJS how to add a loading animation when initially entering the site and also when there is any loading time when rendering a new component.
So is there a way to let people on my website already, although it's not fully loaded, so I can add a loading page with some CSS3 animation as a loading screen.
The question is not really how to make a loading animation. It's more about how to integrate it into ReactJS.
Thank you very much.
Since ReactJS virtual DOM is pretty fast, I assume the biggest load time is due to asynchronous calls. You might be running async code in one of the React lifecycle event (e.g. componentWillMount).
Your application looks empty in the time that it takes for the HTTP call. To create a loader you need to keep the state of your async code.
Example without using Redux
We will have three different states in our app:
REQUEST: while the data is requested but has not loaded yet.
SUCCESS: The data returned successfully. No error occurred.
FAILURE: The async code failed with an error.
While we are in the request state we need to render the spinner. Once the data is back from the server, we change the state of the app to SUCCESS which trigger the component re-render, in which we render the listings.
import React from 'react'
import axios from 'axios'
const REQUEST = 'REQUEST'
const SUCCESS = 'SUCCESS'
const FAILURE = 'FAILURE'
export default class Listings extends React.Component {
constructor(props) {
super(props)
this.state = {status: REQUEST, listings: []}
}
componentDidMount() {
axios.get('/api/listing/12345')
.then(function (response) {
this.setState({listing: response.payload, status: SUCCESS})
})
.catch(function (error) {
this.setState({listing: [], status: FAILURE})
})
}
renderSpinner() {
return ('Loading...')
}
renderListing(listing, idx) {
return (
<div key={idx}>
{listing.name}
</div>
)
}
renderListings() {
return this.state.listing.map(this.renderListing)
}
render() {
return this.state.status == REQUEST ? this.renderSpinner() : this.renderListings()
}
}
Example using Redux
You can pretty much do the similar thing using Redux and Thunk middleware.
Thunk middleware allows us to send actions that are functions. Therefore, it allows us to run an async code. Here we are doing the same thing that we did in the previous example: we keep track of the state of asynchronous code.
export default function promiseMiddleware() {
return (next) => (action) => {
const {promise, type, ...rest} = action
if (!promise) return next(action)
const REQUEST = type + '_REQUEST'
const SUCCESS = type + '_SUCCESS'
const FAILURE = type + '_FAILURE'
next({...rest, type: REQUEST})
return promise
.then(result => {
next({...rest, result, type: SUCCESS})
return true
})
.catch(error => {
if (DEBUG) {
console.error(error)
console.log(error.stack)
}
next({...rest, error, type: FAILURE})
return false
})
}
}

Can I send several events on action in flux?

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.

Categories