Nuxt fetch hook api can't access the api folder - api

I have an api that I call in my fetch() hook:
pages/pageOne.vue:
async fetch() {
const { store, error } = this.$nuxt.context;
try {
await store.dispatch("folderOne/apiOne", null, { root: true });
} catch (e) {
error({
message: "error"
});
}
}
then in my store I have apiOne action setup like this:
store/folderOne/index.js:
//importing the api file here
const actions = {
apiOne({ commit }) {
apiFile.apiOne().then(data => {
if(data && data.status === 200){
// then commit
}
})
.catch(error => {
console.log(error);
});
},
}
Then I have a file for my APIs setup like this:
api/apiFile.js:
import axios from "axios";
const httpClient = axios.create({
headers: {
key: 'value
}
});
const baseUrl = process.env.config.baseUrl;
export default {
apiOne() {
return httpClient.get(`${baseUrl}values);
},
}
It doesn't work. But when I call the same apiOne in a #click method it works. Anyone knows what's is wrong and how can I fix it?

your store is and should be a global construct of your application.
it means when you call for a action and your setup is correct, you don't have to handle directives/folders by yourself.
in your case all you should do is dispatch the action in your component like this:
async fetch() {
const { store, error } = this.$nuxt.context;
try {
await store.dispatch("apiOne", null, { root: true });
} catch (e) {
error({
message: "error"
});
}
}

Related

How to create generic service using Vue3 Js

I am working on an application where I am trying to implement common functions which will call an API endpoint here is my implementation using vue3.
Page
<script>
import httpService from "./services/HttpService.vue";
export default {
data() {
return {
weather: undefined,
error : false,
errormessage : "",
};
},
methods : {
async fetchWeather(e) {
if (e.key == "Enter" && this.query) {
let {response} =await get(query,`${weather_url}forecast?city=`); //await axios.get(`${this.url_base}forecast?city=${this.query}`);
this.setResults(response.data);
}else if (e.key == "Enter" && !this.query){
this.error = true;
this.errormessage = 'Please enter name to search!';
}
},
setResults(res) {
if(res.isSuccessful === true){
this.error = false;
this.weather = res.response;
}else{
this.weather = undefined;
this.errormessage = res.response;
this.error = true;
}
}
},
};
</script>
Service
<script>
import axios from "axios";
export default async function GenericService() {
async function get(query,url) {
try {
let response = await axios.get(`${base_url}${url}${this.query}`);
return response;
} catch (err) {
throw new Error(err);
}
}
}
</script>
GlobalUrl
<script>
export const ConstantBaseUrl = {
base_url: "https://localhost:7005/",
};
export const EndPointUrl = {
weather_url : "api/weather/"
}
</script>
Here I want to achieve is there must be a common method that will be used to send a get request to my backend API. My code is not working and I know I am I might be missing something or doing it the wrong way because I am totally new to Vue3.js.

How to use $nuxt.$loading from axios interceptor

I would like to use $nuxt.$loading https://nuxtjs.org/api/configuration-loading/ outside of Vue component. I created central js for hitting APIs.
services/api-client.js
import axios from "axios";
import { state } from '../store/modules/sessions';
const axiosClient = axios.create({
baseURL: process.env.BASE_URL,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Api-Key': state().token
}
});
axiosClient.interceptors.request.use(function (config) {
// show nuxt loading here
return config;
}, function (error) {
return Promise.reject(error);
});
axiosClient.interceptors.response.use(function (response) {
// hide nuxt loading here
if (response.data.status.code != 200) {
throw { message: response.data.status.errorDetail };
} else {
return response;
}
}, function (error) {
// hide nuxt loading here
return Promise.reject(error);
});
export default {
all(path) {
return axiosClient.get(path);
},
show(path) {
return this.all(path);
},
create(path, params) {
return axiosClient.post(path, params);
},
update(path, params) {
return axiosClient.put(path, params);
}
};
and from my index.vue I'm dispatching the actions which trigger the Api Request.
<template>
<div>
<h1> Welcome </h1>
</div>
</template>
<script>
export default {
created() {
this.$store.dispatch('getInsiders', this);
}
}
</script>
The solution to your problem is this code below.
Please try this.
export default function({ $axios, store }: any) {
$axios.onRequest((config: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.start()
return config
})
})
$axios.onResponse((response: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.finish()
return response
})
})
$axios.onError((error: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.finish()
return Promise.reject(error)
})
})
}
Do you really need to declare own axios client?
Standard way how to do this is using nuxt's axios module and then customize it in your plugin.
nuxt.config.js
modules: ['#nuxtjs/axios'],
plugins: ['~/plugins/axios']
~/plugins/axios
export default ({ $axios, redirect }) => {
$axios.onError(error => {
// do anything you need
})
}
The axios module will manage loading status automatically.
Although you still can disable progress for individual requests
Eg from component/action
await this.$axios.$get('/myapi', { progress: false })

Why can't I pass my user_name value into my component? (Auth)

I am trying to pass the name of the user after authentication into a Vue component, but I get a name: undefined value after load.
Here is my AuthService.js:
//config details taken from OAUTH JS doc: https://github.com/andreassolberg/jso
import { JSO, Fetcher } from 'jso';
const client = new JSO({
providerID: '<my-provider>',
default_lifetime: 1800,
client_id: '<my-client-id>',
redirect_uri: 'http://localhost:8080/',
authorization:'<my-auth-server>/oauth/authorize'
//scopes: { request: ['https://www.googleapis.com/auth/userinfo.profile'] }
});
export default {
getProfile() {
// JSO plugin provides a simple wrapper around the fetch API to handle headers
let f = new Fetcher(client);
let url = 'https://www.googleapis.com/auth/userinfo.profile';
f.fetch(url, {})
.then(data => {
return data.json();
})
.then(data => {
return data.user_name;
})
.catch(err => {
console.error('Error from fetcher', err);
});
}
};
Then, in my single file component named MainNav, I have:
import AuthService from "#/AuthService";
export default {
name: "MainNav",
data() {
return {
name: ""
};
},
created() {
this.name = AuthService.getProfile();
}
};
</script>
Anyone have any tips on how I can get the user_name value from the AuthService to my component? I will then need to then display the name in my nav template. Doing a console.log test works fine, just can't return it to my SFC. Also, the JSO library is here: https://github.com/andreassolberg/jso#fetching-data-from-a-oauth-protected-endpoint
Because getProfile returns nothing (undefined). I see you use es6 then you can use async functions
//config details taken from OAUTH JS doc: https://github.com/andreassolberg/jso
import { JSO, Fetcher } from 'jso';
const client = new JSO({
providerID: '<my-provider>',
default_lifetime: 1800,
client_id: '<my-client-id>',
redirect_uri: 'http://localhost:8080/',
authorization:'<my-auth-server>/oauth/authorize'
//scopes: { request: ['https://www.googleapis.com/auth/userinfo.profile'] }
});
export default {
getProfile() {
// JSO plugin provides a simple wrapper around the fetch API to handle headers
let f = new Fetcher(client);
let url = 'https://www.googleapis.com/auth/userinfo.profile';
return f.fetch(url, {}) // return promise here
.then(data => {
return data.json();
})
.then(data => {
return data.user_name;
})
.catch(err => {
console.error('Error from fetcher', err);
});
}
};
And
import AuthService from "#/AuthService";
export default {
name: "MainNav",
data() {
return {
name: ""
};
},
async created() {
try {
this.name = await AuthService.getProfile();
} catch(error) {
// handle
}
}
};
</script>
Or without async (add one more then)
import AuthService from "#/AuthService";
export default {
name: "MainNav",
data() {
return {
name: ""
};
},
created() {
AuthService.getProfile().then((userName) => this.name = userName))
.catch((error) => { /* handle */ })
}
};
</script>

Showing alert from service class in react-native

I have an HTTP service class which is responsible for communication with my Laravel based API. I want the HTTP service class to trigger React Native Alert box when there is a 401 authentication issue. Or 403 Access denied issue or 422 which is validation issue.
I have the basic HTTP service class ready, but I am not able to show an alert from my HTTP service because it is not a react component and I want to know if I can use such a global service class and still trigger Native components.
Below is my code:
import axios from 'axios';
import {
AsyncStorage,
Alert
} from 'react-native';
class HttpService {
async get(url) {
let at = await AsyncStorage.getItem('access_token', (error, accessToken) => {
return accessToken;
});
let data = await axios.get(url, {headers: { 'Authorization': 'Bearer ' + at }})
.then(response => {
console.log('response', response);
return response;
})
.catch(error => {
let message, title = '';
if (!error.response) {
message = error;
}
if (error.response.status === 401) {
message = error.response.data.message;
title = 'Authentication issue'
}
Alert.alert(title, message, [
{ text: 'Ok', onPress: () => console.log(123) }
]);
});
return data;
}
post() {}
}
export default HttpService;
Why not just return the errors too? So you can determine what occurred so the component that makes the HttpService call knows when to show the Alert.
class yourComponent extends Component{
constructor() {
this.state = {
token: null,
isLoaded: false,
error: null
}
}
componentDidMount() {
HttpService.get('someUrl')
.then(res => {
this.setState({
token: res,
isLoaded: true
});
}, (error) => {
this.setState({
error: error,
isLoaded: true
});
})
}
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
// return your Alert here
} else if (!isLoaded) {
// return loading component;
} else {
// return your main component
}
}
}

Too many requests when controlling spinner show/hide from axois interceptors

I have an SPA written in Vue (Webpack) where I want to control the visibility of a spinner based on whether or not the app is currently handling an HTTP request or a response.
Following some tutorials, I came up with the event bus scheme and did this:
Created eventBus.js:
import Vue from 'vue';
export const eventBus = new Vue();
I'm setting my axios interceptors in the created() hook of App.vue. Here's what the necessary functions look like in that component:
data() {
return {
showLoader: false
};
},
created(){
this.setAxiosInterceptors();
// some code removed //
}
},
mounted() {
eventBus.$on('show-loader', () => {
this.showLoader = true;
});
eventBus.$on('hide-loader', () => {
this.showLoader = false;
});
},
methods: {
setAxiosInterceptors() {
var tokenCookieName = this.$store.getters.getCookieNames.apiToken;
var cookieDefaultValue = this.$store.getters.getCookieDefaultValue;
// token expired middleware
this.axios.interceptors.response.use(response => {
var data = response.data;
if(data.info.api_token) {
this.$cookie.set(tokenCookieName, data.info.api_token);
}
if(data.status == 'error' && data.info.login_failed) {
this.$cookie.set(tokenCookieName, cookieDefaultValue);
window.location = '/'; // not possible to use Vue router here
}
eventBus.$emit('hide-loader');
return response;
},
error => {
eventBus.$emit('hide-loader');
console.log('Response interception failed!');
return Promise.reject(error);
});
// attach API token middleware
this.axios.interceptors.request.use(config => {
var apiToken = this.$cookie.get(tokenCookieName);
if (!apiToken) {
apiToken = cookieDefaultValue;
}
config.headers.Authorization = 'Bearer ' + apiToken;
eventBus.$emit('show-loader');
return config;
},
error => {
eventBus.$emit('hide-loader');
console.log('Request interception failed!');
return Promise.reject(error);
}
);
}
}
Please ignore some of the code that isn't relevant to the problem, but I wanted to show how things are set up. Problem is, as soon as I visit my home page, the app keep making the startup GET requests over and over, until my server returns a 429 error.
Interestingly, in my eventBus.$on handlers, if I just do a console.log, this behavior doesn't appear (of course, the spinner doesn't work as well) but as soon as I change a variable or call a vuex action, this infinite reloading starts.
Any clue?
In the main.js file
Vue.prototype.$axios = axios.create(
{
headers:
{
'Content-Type': 'application/json',
},
baseURL: process.env.API_URL
}
);
Vue.prototype.$axios.interceptors.request.use(
config =>
{
eventBus.$emit('show_spin');
let token = getTokenID();
if(token && token.length) config.headers['Authorization'] = token;
return config;
},
error =>
{
eventBus.$emit('hide_spin');
if (error.status === 401) VueRouter.push('/login');
else throw error;
}
);
Vue.prototype.$axios.interceptors.response.use(
response =>
{
eventBus.$emit('hide_spin');
return response;
},
error =>
{
eventBus.$emit('hide_spin');
return new Promise(function(resolve,reject)
{
if (error.config && error.response && error.response.status === 401 && !error.config.__isRetry)
{
myVue.refreshToken(function()
{
error.config.__isRetry = true;
error.config.headers['Authorization'] = getTokenID();
myVue.$axios(error.config).then(resolve,reject);
},function(flag) // true = invalid session, false = something else
{
if(process.env.NODE_ENV === 'development') console.log('Could not refresh token');
if(getUserID()) myVue.showFailed('Could not refresh the Authorization Token');
reject(flag);
});
}
else throw error;
});
}
);
let myVue = new Vue(
{
el: '#app',
data: function()
{
return {
spin_visible: 0, // dynamically show/hide spinner
};
},
created: function()
{
eventBus.$on('show_spin', this.showSpin);
eventBus.$on('hide_spin', this.hideSpin);
},
methods:
{
showSpin: function()
{
this.spin_visible++;
},
hideSpin: function()
{
if(this.spin_visible>0) this.spin_visible--;
},
....
and then in App.vue
<template>
<router-view/>
<div class="spinner" v-show="$root.spin_visible">
<!-- define your spinner here -->
</div>
</template>