I'm using Vue 3.2 <script setup>, If I try to acess Pinia's store inside an API Service It throws the following error;
Uncaught ReferenceError: Cannot access 'store' before initialization at api.js?:9:1 (anonymous) # api.js?9:9
src/services/api.js:
import axios from 'axios';
import store from '../stores/index';
// eslint-disable-next-line no-undef
const api = axios.create({ baseURL: import.meta.env.VITE_APP_API_URL });
if (store) {
const { token } = store;
if (token) {
api.defaults.headers.Authorization = `Bearer ${token}`;
}
}
console.log(api);
export default api;
src/stores/index.ts:
import { defineStore } from 'pinia'
import Project from '../models/Project';
import { grantAuthSshd, revokeAuth, parseJwt } from '../services/auth';
const initialUser = JSON.parse(sessionStorage.getItem('Orcamento:token') || '{}');
const useProject = defineStore('project-store', {
state: () => ({
loading: false as boolean,
}),
actions: {
loadingDataTable(status: ((status: boolean) => void) & boolean) {
this.loadingDataTable = status;
},
}
});
I tried to use Pinia's interceptors but the error persists:
import axios from 'axios';
import useProject from '../stores/index';
const api = axios.create({ baseURL: import.meta.env.VITE_APP_API_URL });
// use interceptors
api.interceptors.request.use(
(config) => {
const { token } = store;
if ({token}) {
api.config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
const store = useProject();
export default api;
The problem is that there is indirect circular dependency between services/api.js and stores/index.ts modules, to the point they cannot be evaluated correctly.
useProject() returns a singleton, one of reasons why a store is wrapped with a function is that this prevents it from being accessed too soon. Pinia stores are supposed to be accessed only after Pinia is initialized, otherwise this would require to evaluate the modules that depend on it in a specific order that isn't easy to achieve.
In this case useProject is supposed to be used in-place, not on module evaluation:
api.interceptors.request.use(
(config) => {
const store = useProject();
const { token } = store;
...
Due to how ES modules work, this allows to resolve circular dependency.
A way to avoid circular dependency is to move this code from services/api.js to another module that stores/index.ts doesn't depend on, e.g. entry point.
Related
I just added a very basic caching system for my app on GET requests via axios-cache-adapter library.
Works great, but there are situations where I would like to get fresh data (especially after updating some value). How can I accomplish this while using this library?
The docs make no mention of cache busting.
Below is my setup.
import Axios from 'axios'
import store from '#/store'
import { setupCache } from 'axios-cache-adapter'
const cache = setupCache({
maxAge: 15 * 60 * 1000
})
const AxiosConfig = Axios.create({
baseURL: process.env.VUE_APP_BASE_API_URL,
adapter: cache.adapter
})
const addToken = (config) => {
const token = store.getters['AuthModule/getAuthToken']
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
}
AxiosConfig.interceptors.request.use(
(config) => {
addToken(config)
return config
},
(error) => {
return Promise.reject(error)
}
)
export default AxiosConfig
You can invalidate the cache entry per request by setting up the cache.invalidate hook to remove the entry based on a flag (e.g., named "clearCacheEntry "):
import { setup } from 'axios-cache-adapter'
const axiosInstance = setup({
cache: {
// Invalidate only when a specific option is passed through config
invalidate: async (config, request) => {
if (request.clearCacheEntry) {
await config.store.removeItem(config.uuid)
}
}
}
})
Then set the clearCacheEntry option when making the request:
axiosInstance.get('https://httpbin.org/get', { clearCacheEntry: true })
.then(response => {
// Response should not come from cache
assert.ok(response.request.fromCache !== true)
})
I have a token in the store, but I can't access it in my axios plugin. I used to simply import the store when using just Vue, really struggling to understand how to do this in Nuxt.js
I need to access a token value from my store, and use it in the 'Authorization' attribute below.
Here's my current code;
// /plugins/axios.js
import axios from 'axios'
import { state } from '../store'
export default () => {
const api = axios.create({
baseURL: process.env.BASE_URL,
headers: {
Authorization: `Bearer` + xxxx ACCESS STORE STATE HERE xxxx,
},
})
return api
}
// nuxt.config.js
...
{
plugins: ['~/plugins/persistedState.client.js', '~/plugins/axios'],
}
...
// store/index.js
export const state = () => ({
token: null,
user: null,
isUserLoggedIn: false,
})
As state is returned as a function from my store/index.js, I can't get this to work and clearly isn't the solution!
What I've tried
Looking at docs and at old posts on this it looks like I need to pass { store } as an argument but I get the error Cannot destructure property 'store' of 'undefined' as it is undefined.
For example...
export default ({ store }) => {
const api = axios.create({
baseURL: process.env.BASE_URL,
headers: {
Authorization: `Bearer` + store.state.token,
},
})
return api
}
I've also tried setting the Authorization header in the store itself as an alternative, but this doesn't have any effect when posting to my server, no authorization header is supplied.
// store/index.js
...
export const mutations = {
setToken(state, token) {
state.token = token
state.isUserLoggedIn = !!token
this.$axios.setHeader('Authorization', '123')
},
At a bit of loss with this and any help would be very much appreciated.
Plugin functions should be pure and should not re-assign the value of $this by using a => (fat arrow).
You can tap into the current $axios instance and set the header whenever a request is made:
// plugins/axios.js
export default function ({ $axios, store }) {
$axios.onRequest(config => {
const { token } = store.state
if (token) {
config.headers.common.Authorization = `Bearer ${token}`
}
})
}
Mind you that $axios is a package provided by #nuxtjs/axios and this.$axios will differ from this.axios if you've manually registered axios by its self.
I decided to play a little bit with nuxt for the first time from scratch to finish.
and now, I am trying to add plugins.
the plugin I am trying to add is for my api. But when I inject it, it throws the error "inject is not a function". This is my code below. Every other thing works to the best of my knowledge.
import Vue from 'vue'
import axios from 'axios'
import get from 'lodash/get'
import cookies from 'js-cookie'
import { BASE_URL } from '../config/config'
export default (context, inject) => {
const saveToken = (token) => {
cookies.set('AuthToken', token)
}
const removeToken = () => {
cookies.remove('AuthToken')
}
const getToken = () => {
cookies.get('AuthToken')
}
const token = getToken() || ''
const config = {
baseURL: `${BASE_URL}/api/v1`,
params: {},
headers: {
Authorization: `Bearer ${token}`
}
}
const service = axios.create(config)
service.interceptors.response.use(
response => response,
(error) => {
// src of error.
const data = get(error, 'response.data', {})
Vue.$store.commit('notifications/setNotification', data)
}
)
const ApiService = {
...service,
removeToken,
saveToken
}
inject('ApiService', ApiService)
}
Okay, So, I was able to fix it.
Apparently, the error was caused because I added the plugin in the module array instead of the plugins array as suggested in the Nuxt docs.
After putting it in the plugins as opposed to putting it as a module like I did previously, my dev server has started working again.
axios is returning wrap function but I want to use axios.CancelToken in it. Any idea on why this is happening?
import axios from "axios";
const instance = axios.create({
baseURL: appUrl,
timeout: 0,
});
instance.interceptors.request.use(
function(config) {
console.log(instance);
}
);
export const $axios = instance;
CancelToken is a static property of axios. It's not an instance property.
If you're using ES6 module imports, an easy way to reference it is by importing it
import axios, { CancelToken } from "axios"
const instance = axios.create({
baseURL: appUrl,
timeout: 0,
});
const source = CancelToken.source()
instance.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel("Operation cancelled by the user.");
This is really just a shorter version of
import axios from "axios"
const CancelToken = axios.CancelToken
I'm attempting to add an Axios plugin to Nuxt as described here, but it doesn't seem to work.
This is my plugins/axios.js file...
export default function({ $axios }) {
console.log('Im in the axios plugin')
$axios.defaults.baseURL = `https://localhost:5001/api`
$axios.defaults.headers = {
Accept: 'application/json',
'Content-Type': 'application/json'
}
$axios.onRequest((config) => {
console.log('Making request to ' + config.url)
})
}
This is my nuxt.config.js
plugins: ['~/plugins/axios'],
modules: ['#nuxtjs/axios']
And this is where I use Axios in a file called services/BookService.js:
import axios from 'axios'
export default {
getBooks() {
return axios.get('/Home')
},
getBooksFiltered(payload) {
return axios.post('/Home/Filters', payload)
}
}
I get the console.log('Im in the axios plugin') from within my plugin, but nothing else. $axios.onRequest doesn't appear to run, and the baseURL doesn't appear to be set correctly when getBooksFiltered is triggered. I get a 404 when it tried to hit the address http://localhost:3000/Home/Filters. As described in my plugin, the address should be https://localhost:5001/api/Home/Filters
I've also tried the following in my nuxt.config.js, but it doesn't work:
axios: {
baseURL: 'https://localhost:5001/api'
}
Any ideas?
Edit
I've modified my services/BookService.js based on the suggestion below to the following...
export default {
getBooks(axios) {
console.log('Im in getBooks')
return axios.get('/Home')
}
}
My action request that makes my api call is the following....
import BookService from '~/services/BookService.js'
export const fetchBooks = (context) => {
console.log('Im in fetchBooks action')
return BookService.getBooks(this.$axios)
.then((response) => {
context.commit('SET_BOOKS', response.data.booksList)
})
.catch((error) => {
console.log(error)
})
}
And my method in my component that calls the actions...
async fetch({ store, error }) {
try {
console.log('Im in index -> fetch')
await store.dispatch('fetchBooks')
} catch (e) {
error({
statusCode: 503,
message: 'Unable to fetch books at this time'
})
}
}
I'm aware that I may be mixing async/await with promises incorrectly but I don't believe it's the cause of this issue.
Console returns the following...
My network tab contains a single request to http://localhost:3000/ which seems incorrect. It should be https://localhost:5001/api/Home based on the plugin and the address specified in the action. It is also never entering $axios.onRequest
The axios-module sets up an Axios instance on the Nuxt app instance. When you import Axios from axios, and use it directly, you're not using the previously setup Axios instance.
To fix the issue, you could either reference the preconfigured Axios instance from window.$nuxt.$axios (only in the browser), or setup your service to take an Axios instance as a parameter:
// services/BookService.js
export default axios => ({
getBooks() {
return axios.get('/Home')
},
getBooksFiltered(payload) {
return axios.post('/Home/Filters', payload)
}
})
// store.js
import BookService from '~/services/BookService.js'
export default {
actions: {
async getBooks({ commit }) {
const books = await new BookService(this.$axios).getBooks()
commit('SET_BOOKS', books)
}
}
}
Another solution from nuxt-community/axios-module #28:
~/plugins/axios-port.js
import { setClient } from '~/services/apiClient'
export default ({ app, store }) => {
setClient(app.$axios)
}
~/services/apiClient.js
let client
export function setClient (newclient) {
client = newclient
}
// Request helpers
const reqMethods = [
'request', 'delete', 'get', 'head', 'options', // url, config
'post', 'put', 'patch' // url, data, config
]
let service = {}
reqMethods.forEach((method) => {
service[method] = function () {
if (!client) throw new Error('apiClient not installed')
return client[method].apply(null, arguments)
}
})
export default service
Use:
import apiClient from '~/services/apiClient'
export default {
async current () {
return apiClient.get('...')
}
}
In my case I exported a customized axios instance as the doc suggested in my axios.js
export default function ({ $axios }, inject) {
const api = $axios.create({
baseURL:'/api'
})
// Inject to context as $api
inject('api', api)
}
Then use this.$api.get or this.$api.post in your getBook service
The above one works for me
As I have just tested, in each request we should use $axios.
Example: this.$axios.get('....'), or in another context this.$nuxt.$axios.get('...');
Because axios extension use with the app context instance, if we import, it will create a new instance which plugin cannot extend.
I have put test code on stackblitz: here
It seems you need to yarn add #nuxtjs/axios or npm install #nuxtjs/axios like the setup instruction here before it can work: https://axios.nuxtjs.org/setup
I haven't experienced with nuxt yet but I don't think by adding some line of code into some js file without actually installing will make the package available into your repo.