Axios client with useSwr Fails to make request - javascript

Im using a shared Axios "client" object that is passed around my application via react context, this client object has the API key auth header and base paths already configured so im not constantly defining it.
My problem is trying to use the useSwr hook, specifically when defining the fetcher. I just cannot get it to work, and im sure im missing something simple here.
Basically, I pull the api client off the context, and use a fetcher function iv defined already, but I get nothing.
Here's some snips,
The Client
const AXIOS_CLIENT_CONFIG = {
baseURL: API_BASE,
timeout: 2000,
};
export default class APIClient {
client: AxiosInstance;
accessToken: string;
headers: any;
constructor(accessToken?: string) {
this.accessToken = accessToken;
this.headers = { Authorization: `Bearer ${accessToken}` };
if (accessToken) {
this.client = axios.create({
...AXIOS_CLIENT_CONFIG,
headers: this.headers,
});
} else {
this.client = axios.create(AXIOS_CLIENT_CONFIG);
}
}
fetcher(url: string): Promise<any> {
return this.client.get(url).then((res) => res.data);
}
The Component
export default function Upload(): ReactElement {
const { api }: IAppContext = useContext(AppContext);
const { data, error } = useSwr(`/upload/${uploadId}`, api.fetcher, {
refreshInterval: 5000,
});
Using above, I see nothing, no requests, no errors. (yes, the client comes through fine, I use this throughbout my whole app, its just this fetcher part that is broken)
Just for testing if I define the following fetcher, I can see a request is made (and failed due to auth)
const fetcher = (url) => axios.get(url).then((res) => res.data);
Even logging out the function signatures, they look almost the same to me
console.log("API FETCHER", api.fetcher);
console.log("NORMAL FETCHER", fetcher);
Outputs
API FETCHER ƒ fetcher(url) {
return this.client.get(url).then(function (res) {
return res.data;
});
}
NORMAL FETCHER ƒ fetcher(url) {
return axios__WEBPACK_IMPORTED_MODULE_5___default().get(url).then(function (res) {
return res.data;
});
}
What am I doing wrong here?

After hours of screwing around, I eventually figured this out. Incase anyone else comes across the issue when trying to use an Axios client objection with class functions like I am here.
I had no bound the context of this within the class for that specific function.
Basically, I needed to add the following to my api clients constructor
// Bind "this" context
this.fetcher = this.fetcher.bind(this);

Related

How to use same token until expires?

In the code below, I will call LoginAPI for authorization and writes token as a state(Login.e2e.ts ).By the way using axios interceptors.request in my axios.ts file.
My question is;
If I use below code logic, when I send request with customAxios in my project,everytime await LoginAPI.API.Signin.run() will run for every API request. Soon,I can have 100 API call. I don t want run every time await LoginAPI.API.Signin.run(),because I can take 429 error.
The new logic should be like this;
I want to take a token first and then use it until it expires. If the token expired then send a new request and get a new token. How can do this using JavaScript or TypeScript?
This is my Login.e2e.ts file
import api from "api/core"
import { expect } from "#playwright/test";
export const LoginAPI = {
States: {
token: {} as string
},
API: {
Signin: {
notes: "user login",
run: async () => {
let res: any = await api.test.LoginPost(process.env.NAME, process.env.PASS)
LoginAPI.States.token = res.data.token
expect(res.status).toBe(200)
},
},
},
};
This is my axios.ts file
import axios from "axios";
import { LoginAPI } from "../playwright/tests/login/login.api";
const customAxios = axios.create({
baseURL: process.env.ENV === '1' ? "https://test1" : process.env.ENV === '2' ? "https://test2" : "https://test3",
});
customAxios.interceptors.request.use(
async (config) => {
await LoginAPI.API.Signin.run()
if (config.headers) {
config.headers['Authorization'] = `Bearer ${LoginAPI.States.token}`;
return config;
}
return config;
},
(error) => {
Promise.reject(error);
}
);
export default customAxios
take a token first and then use it until it expires. If the token expired then send a new request and get a new token.The above code should be changed the this logic
I would suggest you to log in once and get cookies from the browser.context() and save them as JSON file then use this cookie state/session for the rest of the test. That way you won't have to log in every time for new tests or test suites.
More information using storageState(options) here in official docs.
Example of using storageState(options) in your global setup and teardown here in official docs.

Sveltekit POST method not allowed on endpoint

I am trying to get this fetch method to work in Svelte
When the page.svelte calls the function to fetch the data, in the console I receive this
[HTTP/1.1 405 Method Not Allowed 19ms]
Which I have narrowed down to this
POST method not allowed
Is there a variable I need to set in the config files or is there another solution which I am missing?
// svelte.config.js
import adapter from '#sveltejs/adapter-auto';
const config = {
kit: {
adapter: adapter(),
methodOverride: {
allowed: ['POST']
},
}
};
export default config;
Both files are in the Routes folder
// fetch-data.js
export const POST = async(data) => {
const response = // get data function here
return {
body: {
data: response
}
}
}
// page.svelte
async function fetchData() {
const response = await fetch('./fetch-data', {
method: 'POST',
body: JSON.stringify(),
headers: {
'content-type': 'application/json'
}
})
const { data } = await response.json()
return data
}
The solution was to change the POST variable name to lowercase in the get-data.js file
I had the same error for a different reason, and the syntax in your question and answer has changed again so I'll add my discoveries:
+page.server.ts should no longer export const POST but instead export const actions: Actions = { ... } which don't need to return anything.
+server.ts still uses function POST(event: RequestEvent) and needs to return a Response (from the Fetch API).
Cookies can no longer be set with setHeaders but must use cookies instead. Otherwise the handler also returns 405.
I had the same problem because of old SvelteKit version (I used .456, current version at the time this was written is .484). So make sure you have the most recent version of framework, when referring to the docs ;)

Next js data fetching with axios instance and Authorization

I'm using NextJS 12.0.10 with next-redux-wrapper 7.0.5
And Axios custom instance to hold user JWT token saved in local storage and inject it with every request also to interceptors incoming error's in each response
The problem with this is that I simply cannot use the Axios instance inside the Next data fetching methods
Because there is no way to bring user JWT Token from local storage when invoking the request inside the server
Also, I cannot track the request in case of failure and send the refresh token quickly
I tried to use cookies but getStaticProps don't provide the req or resp obj
Should I use getServerSideProps always
axios.js
const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 20000,
headers: {
common: {
Authorization: !isServer()
? localStorage.getItem("access_token")
? "JWT " + localStorage.getItem("access_token")
: null
: null,
accept: "application/json",
},
},
});
login-slice.js
export const getCurrentUser = createAsyncThunk(
"auth/getCurrentUser",
async (_, thunkApi) => {
try {
const response = await axiosInstance.get("api/auth/user/");
await thunkApi.dispatch(setCurrentUser(response.data));
return response.data;
} catch (error) {
if (error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
toast.error(error.message);
return thunkApi.rejectWithValue(error.message);
}
}
);
Page.jsx
export const getStaticProps = wrapper.getStaticProps((store) => async (ctx) => {
try {
await store.dispatch(getCurrentUser());
} catch (e) {
console.log("here", e);
}
return {
props: {},
};
});
Server side rendered technology is a one-way street if you follow the standard practise. You won't get any local details - being it cookies, local store or local states back to the server.
I would let the server build the DOM as much as it makes sense (ie with empty user data) and let the client fetch the data via useEffect.

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.

how to modularize rest api calls using axios in vuejs

I am creating a vuejs app where I am using axios to consume my rest api.
I am basically calling axios.get in various places, every time creating a new instance with the required authentication headers.
// UserdataComponent.vue
const anInstance = axios.create({
headers: {'X-API-TOKEN': store.state.token},
auth: {
username: SOME_USERNAME,
password: SOME_PASSWORD
}
})
anInstance.get(API_BASE_URL + '/userdata')
This is being done everywhere I make a rest api call.
So I wanted to move this to a separate file to keep the code DRY.
I moved the axios instance creation code to a separate file and tried exporting it as an object. This object can then be imported wherever I want to consume rest api.
I was expecting something like this to work....
// http.js
import axios from 'axios'
import store from 'store/store.js'
const HTTP = axios.create({
baseURL: API_BASE_URL,
headers: { 'X-API-TOKEN': store.state.token },
auth: {
username: SOME_USERNAME,
password: SOME_PASSWORD
}
})
export default HTTP
// UserdataComponent.vue
import HTTP from 'http.js'
...
HTTP.get('/userdata')
This gave me errors of all sorts with axios.create being returned as a string, instead of a callable function.
What should I be changing here to make it work as I want to? Should I even be using this way to modularize the http request mechanism?
Not sure if this answers you question but it's a nice way of setting it up.
If you create the axios instance in a separate file, you could export specific api calls instead, making them accessible for other components as well.
// api.js
const HTTP = axios.create({
baseURL: API_BASE_URL,
headers: { 'X-API-TOKEN': store.state.token },
auth: {
username: SOME_USERNAME,
password: SOME_PASSWORD
}
})
export default {
getSomeData () {
return HTTP.get('/endpoint')
},
postSomeData (id, name) {
return HTTP.post('/endpoint', {
id: id,
name: name
})
}
}
and then, in your component you import the api.jsand use like this:
//component.vue
import myApi from '../path/to/api'
export default {
name: 'myComponent',
methods: {
someMethod () {
myApi.getSomeData().then((response) => {
...code
})
}
}
}
I think you should use axios interceptors for this:
Axios.interceptors.request.use((config) => {
// Add stuff to the config..
// Add credentials to each request.
config.withCredentials = true
config.timeout = 10000
config.headers['Accept-Language'] = i18n.locale
config.headers['Content-Type'] = 'application/json'
return config
})
You can place this code in your main file.
Each time you do a request, this code is called and you can add your credentials to the request, so you don't have to pass the same code everywhere...
For more information check https://github.com/axios/axios#interceptors and on the web around this subject.

Categories