Nuxt plugin throwing inject is not a function - javascript

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.

Related

Cannot call store inside an API with Pinia

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.

Overriding Cached routes in Axios Cache Adapter

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

Nuxt.js - accessing store state in axios plugin

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.

Can not get a response from an API with axios using authorization

I am trying to pull a recipe list from a server that is expecting an auth token from local storage. I created a local axiosWithAuth file because I am going to be hitting multiple endpoints from the API. My auth file looks like this...
import axios from 'axios';
const axiosWithAuth = () => {
const token = localStorage.getItem('token');
return axios.create({
baseURL: 'https://secret-fam-recipes.herokuapp.com/api',
headers: { Authorization: token }
});
};
export default axiosWithAuth
I am then using this axios call with Redux actions so that the data that is returned updates the store for my project. I have tried calling the API two different ways. One with async await which looks like this...
export const listRecipes = () => async (dispatch) => {
dispatch({type: actions.FETCH_RECIPES_REQUEST});
try {
const {recipes} = await axiosWithAuth.get("/recipes")
console.log("recipes", recipes)
dispatch({type: actions.FETCH_RECIPES_SUCCESS, payload: recipes})
} catch(error) {
dispatch({type: actions.FETCH_RECIPES_FAILURE,
payload:
error.resposne && error.response.message
? error.response.message
: error.message
})
}
}
and another without async await which looks like this...
export const listRecipes = () => (dispatch) => {
dispatch({type: actions.FETCH_RECIPES_REQUEST});
axiosWithAuth.get("/recipes")
.then(data => {
return dispatch({type: actions.FETCH_RECIPES_SUCCESS, payload: data})
})
.catch(error => {
dispatch({type: actions.FETCH_RECIPES_FAILURE,
payload:
error.resposne && error.response.message
? error.response.message
: error.message
})
})
}
I also imported my dependencies like so...
import axiosWithAuth from "../utils/axiosWithAuth";
import * as actions from "../constants/recipeConstants";
When I try the async await action I get a 500 error back from the server. When I use the non async await action I get a type error when trying to load the page that says the following..
TypeError: _utils_axiosWithAuth__WEBPACK_IMPORTED_MODULE_0__.default.get is not a function
so obviously there is something wrong with my actions or my axiosWithAuth format. I have tried changing my axiosWithAuth function to use the auth keyword instead of headers but it does not work still. I am not sure if my action is written improperly or if there is something else I should look at. Any help would be greatly appreciated. Thank you!
I was just working with someone and we figured it out. axiosWithAuth is a function so I should have called the function in my action. The correct line is
const {recipes} = await axiosWithAuth().get("/recipes")
More short approach is not to return a function from your custom axios but return the actual axios instance. Since you're just setting a header, you can do this:
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: 'https://secret-fam-recipes.herokuapp.com/api'
});
axiosInstance.interceptors.request.use((config) => {
config.headers['Authorization'] = localStorage.getItem('token');
return config;
});
export default axiosInstance;
And then, just import and use the axios instance:
import axios from './custom-axios';
axios.get('/url-here');

Unable to use an axios plugin in nuxt

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.

Categories