I have separated my api call into three layers. The component, the repository, and the apihelper. I want the logic for refresh_tokens to be in apihelper.js. When I do this it seems like the apihelper runs again after getting the 401 response status but it never passes the data back up to the component. I know I could put the logic to rerun it in the component but that seems like it will end up being a lot of duplicate code as I add more calls. I feel like it's probably caused by my shallow understanding of javascript promises but I'm a javascript beginner.
Component
<script>
import breweryrepository from '#/repository/breweryrepository.js'
export default {
mounted() {
this._getTotalBreweries();
},
methods: {
_getTotalBreweries() {
breweryrepository.getTotalBreweries()
.then((response) => {
if(response.data)
{
this.totalNumberOfBreweries = response.data.totalBreweries;
}
})
}
},
data () {
return {
totalNumberOfBreweries: ''
}
}
}
</script>
Repository
import apihelper from '#/helpers/ApiHelper.js';
export default {
getTotalBreweries() {
return new Promise(function(resolve, reject) {
resolve(apihelper.apiCall('/brewery/totalnumber'));
});
}
}
Apihelper
import axios from 'axios';
var querystring = require('querystring');
import { store } from '../store/store.js';
import auth from '#/auth/auth.js'
export default {
apiCall(url) {
return axios.get(store.state.baseUrl + url, { 'headers': auth.getAuthHeader() })
.catch((error) => {
if(error.response.status == 401)
{
console.log("401 error, running refresh and apicall again");
auth.refreshToken();
this.apiCall(url);
}
})
}
}
Aaaaand I wasn't returning the call.
return this.apiCall(url);
Works now
Related
I don't understand a bit how to make such a video correctly.
The cookie stores data, and when entering the page, it should be checked for access
/page/post.ts/
import CheckToken from '../../../utils/CheckToken';
export async function getServerSideProps(context) {
CheckToken(context.req, context.res);
return {
props: {
tabledb,setting
}, // will be passed to the page component as props
}
}
export default function post() {
return ()
}
..../../../utils/CheckToken
export default async function handler(req,res) {
try {
...//Data
} catch(err) {
res.redirect(401,"/admin/login" )
}
}
If it fails or if there is an error, I need to redirect to the authorization page
But res.redirect doesn't work outside of the api. Tell me then how to make a check then?
Your CheckToken function should return some kind of result to indicate if the call was successful or not, then based on that you can do a redirect.
export async function getServerSideProps(context) {
const result = CheckToken(context.req, context.res);
// if "falsy" do the redirect
if (!result) {
return {
redirect: {
destination: '/signin',
permanent: false,
},
}
}
return {
props: {
tabledb,setting
}, // will be passed to the page component as props
}
}
I have 2 actions that make GET requests and save the response in the Vuex store. The first action getVersion() gets the most recent version of the game and that version is required in order to make the second GET request. Right now I've hard coded the version in the second action, however, my goal is to concatenate it inside the URL.
Sadly I'm not sure how to access it from inside the function. Console.log(state.version) returns null for some reason even though it shouldn't be. I call these functions from inside App.vue like this:
mounted(){
this.$store.dispatch('getVersion')
this.$store.dispatch('getChampions')
}
Vuex store
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
version: null,
champions: null
},
mutations: {
version(state, data){
state.version = data.version
},
champions(state, data){
state.champions = data.champions
}
},
actions: {
getVersion({commit}){
axios.get("http://ddragon.leagueoflegends.com/api/versions.json")
.then((response) => {
commit('version', {
version: response.data[0]
})
})
.catch(function (error) {
console.log(error);
})
},
getChampions({commit, state}){
axios.get("https://ddragon.leagueoflegends.com/cdn/9.24.1/data/en_US/champion.json")
.then((response) => {
commit('champions', {
champions: response.data.data
})
})
.catch(function (error) {
console.log(error);
})
}
},
getters: {
version: (state) => {
return state.version;
},
findChampion: (state) => (id) => {
let championId = id.toString();
let champion = Object.values(state.champions).find(value => value.key === championId);
return champion
}
}
})
With this part:
this.$store.dispatch('getVersion')
this.$store.dispatch('getChampions')
The second dispatch doesn't wait for the first one to finish. Meaning that it is firing before the first one has had a chance to finish getting the version.
You need to create a promise that should resolve before the second dispatch is called.
You could try doing it this way:
async mounted(){
await this.$store.dispatch('getVersion')
await this.$store.dispatch('getChampions')
}
or if you don't want to use async/await
this.$store.dispatch('getVersion').then(() => {
this.$store.dispatch('getChampions');
});
And in the action you should add return to the request (this is important):
return axios.get(...
dispatcher returns a promise
this.$store.dispatch('getVersion').then(()=>{
this.$store.dispatch('getChampions');
});
I can do this with axios, as far it returns promise which I can resolve later. Example with axios:
in api.js file I have this:
import axios from 'axios';
export default {
listUsers() {
return axios.get(`api/users`);
},
addUser(data) {
return axios.post(`api/adduser`, data);
}
}
And then I use this api object in my later code and it works good:
file usersActions.js:
import api from '../api';
const UsersActions = {
...
loadUsers() {
api.listUsers()
.then(({ data }) =>
AppDispatcher.dispatch({
type: Constants.LOAD_USERS_SUCCESS,
users: data
})
)
...
}
};
But can I do something like this with jquery.ajax?
React (from create-react-app) with MobX. Using axios for async backend API calls.
This code works. The initial state (array of issues) is populated, and the webpage presenting this component renders with initial content from state.
import { observable, computed, autorun, reaction } from 'mobx'
import axios from 'axios'
class IssuesStore {
#observable issues = []
constructor() {
autorun(() => console.log("Autorun:" + this.buildIssues))
reaction(
() => this.issues,
issues => console.log("Reaction: " + issues.join(", "))
)
}
getIssues(data) {
return data.map((issue) => ({title: issue.name, url: issue.url, labels: issue.labels}))
}
#computed get buildIssues() {
const authToken = 'token ' + process.env.REACT_APP_GH_OAUTH_TOKEN
axios.get(`https://api.github.com/repos/${process.env.REACT_APP_GH_USER}/gh-issues-app/issues`,
{ 'headers': {'Authorization': authToken} })
.then(response => {
console.log(response)
this.issues = this.getIssues(response.data)
return this.issues
})
.catch(function(response) {
console.log(response)
})
}
}
export default IssuesStore
In an attempt to separate API invocation promises from individual components and stores, I pulled out the axios call into a separate js file, as a collection of functions:
import axios from 'axios'
const authToken = 'token ' + process.env.REACT_APP_GH_OAUTH_TOKEN
export function loadIssues() {
return this.apiPromise(
`https://api.github.com/repos/${process.env.REACT_APP_GH_USER}/gh-issues-app/issues`,
{ 'headers': {'Authorization': authToken} }
)
}
export function apiPromise(endpoint, options) {
return axios.get(endpoint, options)
.then((response) => {
// console.log("response: " + JSON.stringify(response, null, 2))
return response.data.map((issue) => ({title: issue.name, url: issue.url, labels: issue.labels}))
})
.catch(function(response) {
console.log(response)
})
}
Now, my store looks like this:
import { observable, computed, autorun, reaction } from 'mobx'
import * as github from '../api/github'
class IssuesStore {
#observable issues = []
constructor() {
autorun(() => console.log("Autorun:" + this.buildIssues))
reaction(
() => this.issues,
issues => console.log("Reaction: " + issues.join(", "))
)
}
#computed get buildIssues() {
this.issues = github.loadIssues().data
return this.issues
}
}
export default IssuesStore
Much smaller... but the webpage now throws an error because it now sees the initial state of issues as undefined on first render.
Uncaught TypeError: Cannot read property 'map' of undefined
The promise completes successfully later on (as it should), but by then it's too late. Sure, I can set up a few null checks in my rendering components to not run .map or other such functions on empty or as-yet-undefined variables.
But why does the code work with no initial rendering errors before the refactoring, and not after? I thought the refactoring was effectively maintaining the same logic flow, but I must be missing something?
In your refactored version
github.loadIssues().data
Is always going to be undefined because the data property on that Promise will always be undefined.
In the original version, this.issues was only ever set once data returned from the api, so the only values that it was ever set to were the initial value [] and the filled array from the api response.
In yours, the three states are [] -> undefined -> and the filled array.
buildIssues should look something like this:
#computed get buildIssues() {
github.loadIssues().then((data) => {
this.issues = data
}).catch((err) => {
// handle err.
})
}
I am trying to make my Vue app have server-side rendering. I am using vue-server-renderer (https://www.npmjs.com/package/vue-server-renderer). Client-side rendering is working fine.
My app use vue-router and axios
Here is my server.js:
server.get('*', (request, response) => {
bundleRenderer.renderToString({ url: request.url }, (error, htmlPromise) => {
if (error) {
// Log the error in the console
console.error(error)
// Tell the client something went wrong
return response
.status(500)
.send(error)
}
response.send(layout.replace('<div id=app></div>', htmlPromise))
})
})
getInfo() is the method to fetch server data.
Here is getInfo():
export default {
methods: {
getInfo(api) {
return axios
.get(api || this.$route.params.path)
.then((data) => {
this.data = data
this.$set(this, 'isLoading', false)
})
},
},
}
My server entry is:
import { app, router, store } from './index'
export default context => {
let componentPromises = router.getMatchedComponents().filter((component) => {
return component.methods && component.methods.getInfo
}).map((component) => {
return component.methods.getInfo()
})
return Promise.all(componentPromises).then(() => {
return app
})
}
However, I soon realize that all the components from router.getMatchedComponents() does not have $route or $set. Therefore, the method getInfo() stops working.
The document from https://router.vuejs.org/en/api/router-instance.html is very short and does not provide much information:
router.getMatchedComponents()
Returns an Array of the components (definition/constructor, not
instances) matched by the current route. This is mostly used during
server-side rendering to perform data prefetching.
How can I fix the problem?
I have previously incurred into a similar problem and managed to successfully prefetch data by doing the following:
app.$router.onReady(() => {
const matchedComponents = app.$router.getMatchedComponents()
if (!matchedComponents.length) { /* ... */}
Promise.all(matchedComponents.map((Component: any) => {
if (Component.options.methods.asyncData) {
return Component.options.methods.asyncData({
store: app.$store,
route: app.$router.currentRoute
});
}
})).then(() => { /* your callback here ... */ });
}
According to vue ssr documentation (https://ssr.vuejs.org/en/data.html) the suggested way is to use a custom asyncData method in your component to perform data fetching rather than calling component methods directly:
export default {
asyncData ({ store, route }) {
// return the Promise from the action
return store.dispatch('fetchItem', route.params.id)
}
},