I was handed a base to develop a project on. It's made in Vue and Typescript, which doesn't have much support online.
I currently need to make several API calls in order to check the availability of a service, and must do those inside a component. For some reason, I'm not able to figure how to do so.
What I currently have is:
import * as Vue from 'vue';
import { Component } from 'vue-property-decorator';
import Axios, { AxiosInstance, AxiosRequestConfig, AxiosError, AxiosResponse, AxiosStatic } from 'axios';
#Component({
(...)
})
export class Something extends Vue {
public $http: Axios;
constructor() {
super();
this.$http = Axios.create({
baseURL: "https://swapi.co/api/people/1/"
});
}
testFunc() {
let data: any;
this.$http.get("https://swapi.co/api/people/1/", data)
.then((res: AxiosResponse) => {
console.log(res.data);
})
.catch((ex: any) => {
console.log(ex);
});
}
}
There's several things that I've changed in order to get this to work, thus the code I've pasted counts more as a structure than anything else. I also have a button in my view that calls that testFunc(). Also, Axios doesn't get recognized as a type, and even if I import "axios" instead, it doesn't work. AxiosInstasnce does work, but gets me nowhere.
I'm encapsulate HTTP/REST operations in separate .ts files and which I then call form a component or from the Vuex store. Here I also use async/await to have better readable code. Each method declared its input and return types.
import axios from 'axios'
const http = axios.create({
baseURL: `${SOME_SERVICE_URL}/base-path`,
headers: { 'Content-Type': 'application/json' }
})
export async function getItemById (itemId: string): Promise<Item> {
const response = await http.get(`/${itemId}`)
return response.data
}
export async function createItem (item: Item): Promise<Item> {
const response = await http.post('/', JSON.stringify(item))
return response.data
}
Related
I would like to have following, but working code in my http-axios file:
import axios from "axios";
import {useAuthContext} from './services/useAuthContext'
const {user} = useAuthContext();
export default axios.create({
baseURL: "http://localhost:4000/api/",
headers: {
"Content-type":"application/json",
"Authorization": `Bearer ${user.token}`
}
});
I can not understand how to make my code working because of the continuous dependencies i have.
I call this axios.create export later in an ./services/imgs.js file:
import http from "../http-axios";
class VMsDataService {
getLatest(page=0){
return http.get(`vms/latest`);
}
}
which i use in App.js file:
import VMsDataService from "./services/vms";
VMsDataService.getLatest()
.then(response => {
//
}).catch(e => {
//
});
So my question is. How can i restructure my code to be able to retrieve dynamic data within my axios config and also have this class based functionality i'm having right now. Main point of this is to have dynamic Authorization header taken from useAuthContext component
I tried to turn everything into components but it wasn't successful.
A way to create a reusable component, is to create a custom hook.
For example a custom hook that will return user object and set Authorization :
import axios from "axios";
import {useAuthContext} from './services/useAuthContext'
const useAxios = () => {
const {user} = useAuthContext();
return axios.create({
baseURL: "http://localhost:4000/api/",
headers: {
"Content-type": "application/json",
"Authorization": `Bearer ${user.token}`,
},
});
};
export default useAxios;
Then you can use this custom hook in VMsDataService.
I am not used to class component, so I wrote it in functional component. I think it is a lot easier :
import React from "react";
import useAxios from "../hooks/useAxios";
const VMsDataService = () => {
const http = useAxios();
const getLatest = (page = 0) => {
return http.get(`vms/latest`);
};
return {getLatest};
};
export default VMsDataService;
Hope I understand clearly what you were asking for !
In my react application, I'm defining an Axios class with a bunch of methods, but the methods are not being recognized as functions and throwing an error. Showing is easier than explaining so... I have 3 files involved...
http-common.js has this:
import axios from 'axios';
export default axios.create({
baseURL: "http://localhost:5000/api/v1/tours",
headers: {
"Content-type": "application/json"
}
});
tours.js has this:
import http from "../http-common";
class ToursDataService {
getAll(page = 0) {
return http.get(`?page=${page}`);
}
}
export default ToursDataService
tours-list.js has this... which calls the function "getAll" in retrieveTours.
import React, { useState, useEffect } from "react";
import ToursDataService from "../services/tours";
const ToursList = props => {
const [tours, setTours] = useState([]);
useEffect(() => {
retrieveTours();
}, []);
const retrieveTours = () => {
ToursDataService.getAll()
.then(response => {
setTours(response.data.tours)
})
.catch( e => {
console.log(e);
});
}
The console claims that getAll is not a function. Why? Can anyone explain?
scheduler.development.js:173 Uncaught TypeError: _services_tours__WEBPACK_IMPORTED_MODULE_1__.default.getAll is not a function
at retrieveTours (tour-list.js:12:1)
getAll() is not a static method so you'd need to create an instance of ToursDataService...
const svc = new ToursDataService(); // create an instance
// ...
svc.getAll() // call the method on the instance
.then(...)
or make the method static
class ToursDataService {
static getAll(page = 0) {
return http.get("", { params: { page } });
}
}
Alternately, don't use classes at all since you don't appear to be encapsulating anything. You might as well just export the getAll function on its own
// tours.js
export const getAll = (page = 0) => http.get("", { params: { page } });
and
import { getAll } from "../services/tours";
I'm new to React and I have this function.
import Axios from "axios";
const UserService = {
getUserRole: (access_token: string = "") => {
return Axios({
method: "get",
url: "https://<url>/user/role",
headers: {
"Authorization": `Bearer ${access_token}`
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
}
}
export default UserService
The getUserRole is used constantly by another component, for example
import UserService from "../../../services/authentication/userService";
import { useAuth } from "react-oidc-context";
...
const auth = useAuth();
UserService.getUserRole(auth.user?.access_token);
As you can see, I have to constantly pass the access_token from useAuth. Is there any way I can call useAuth inside my UserService so I don't have to constantly pass the access_token from my component?
The premise of the question is backward, as we shouldn't try to use hooks outside of React, but instead use outside code inside of React.
Quick solution: Custom hook
If the roles are used all over the place, a quick custom hook will get you started. This is the easiest way to wrap custom logic as hooks are meant to wrap stateful logic for reuse in components.
import { useState, useEffect } from "react";
import { useAuth } from "react-oidc-context";
import UserService from "../../../services/authentication/userService";
/**
* Custom hooks that fetches the roles for the logged in user.
*/
const useRoles = () => {
const auth = useAuth();
const [roles, setRoles] = useState();
useEffect(() => {
if (!user) return; // pre-condition
UserService
.getUserRole(auth.user.access_token)
.then(setRoles);
}, [auth.user]);
return roles;
}
Then in any component:
import useRoles from "../useRoles";
const MyExampleComponent = () => {
const roles = useRoles();
if (!roles) return <span>Please login (or something) to see the roles!</span>
return <div>{/* use roles here */}</div>
}
Better solution: Service provider
If there's a lot of different methods on the user service that needs to be used all over the app, then wrapping the whole service and providing a ready-to-use version through React's context would be best in my opinion.
But first, let's rework the UserService a little so that it uses a local axios instance instead of the global axios instance.
// I also made it a class, but it would also work with an object.
class UserService {
constructor(axios) {
this.axios = axios;
}
getUserRole(){
// use the local axios instance
return this.axios({
method: "get",
// Use the default URL from local axios instance
url: "user/role",
})
.then(({ data }) => data)
.catch(console.log),
}
getSomethingElse() {
// ...
}
}
Then, we can setup the React's context for the user service.
// UserServiceContext.js
import React from 'react';
import { useAuth } from "react-oidc-context";
import UserService from "../../../services/authentication/userService";
// Local axios instance
const axiosInstance = axios.create({
baseURL: 'https://<url>', // set the base URL once here
});
const userServiceInstance = new UserService(axiosInstance);
const UserServiceContext = React.createContext(userServiceInstance);
// Convenience hook
export const useUserService = () => useContext(UserServiceContext);
export const UserServiceProvider = (props) => {
const auth = useAuth();
useEffect(() => {
// If the user changes, update the token used by our local axios instance.
axiosInstance.defaults.headers
.common['Authorization'] = `Bearer ${auth.user?.access_token}`;
}, [auth.user]);
return (
<UserServiceContext.Provider value={userServiceInstance} {...props} />
);
}
Then anywhere, but commonly at the App's root:
import { AuthProvider } from "react-oidc-context";
import { UserServiceProvider } from "./UserServiceContext";
const App = () => (
<AuthProvider>
<UserServiceProvider>
<Content />
</UserServiceProvider>
</AuthProvider>
);
Now everything is ready to be used in any component!
import { useUserService } from '../UserServiceContext';
const MyExampleComponent = () => {
const userService = useUserService();
const [roles, setRoles] = useState();
// e.g. load roles once on mount.
useEffect(() => {
userService // use the service from the context
.getUserRole() // no auth token needed anymore!
.then(setRoles);
}, []);
if (!roles) return <span>Please login (or something) to see the roles!</span>
return <div>{/* use roles here */}</div>
}
Note that a custom hook could still be used to wrap the roles fetching logic. Both the context and hooks can be used together to wrap logic to each's own preferences.
// Here's what the hook could look like if it used the new provider above.
const useRoles = () => {
const userService = useUserService();
const [roles, setRoles] = useState();
// e.g. load roles once on mount.
useEffect(() => {
userService // use the service from the context
.getUserRole() // no auth token needed anymore!
.then(setRoles);
}, []);
return roles;
}
I consider the provider solution to be better since it provides more flexibility while keeping control over the exposed API.
In my solution, I suggest using the UserService instance as the provided value, but the provider could be changed to expose only parts of the API, or it could provide the roles and other data automatically. It's up to you!
Disclaimer: I've used minimal code to demonstrate a working solution and my answer may not address all constraints of your situation. For example, the axios instance could be created inside the provider as a lazy initialized useRef, same thing goes for the UserService instance, etc.
I am using axios in my create-react-app. Which is the best way to use axios:
Method 1:
ajax.js
import axios from 'axios';
const axiosInstance = axios.create({});
export default axiosInstance;
app.js
import ajax from './ajax.js';
ajax.post('url');
Method 2:
ajax.js
import axios from 'axios';
class AjaxService{
constructor(apiConfig){
this.service = axios.create(apiConfig);
}
doGet(config){
return this.service.get(config.url);
}
...
}
export default AjaxService;
app.js:
import AjaxService from './ajax';
const service1 = new AjaxService();
service.doGet({url:'url'});
app2.js
import AjaxService from './ajax';
const service2 = new AjaxService();
service.doGet({url:'url'});
In method 2, we have to initialize the service wherever we make a call, which may or may not be a best practice. If we follow method 2, Is there a way to make it as a common service across the application?
i've seen a way in here and i came up with another solution like i explained below:
1 - i created my service with axios
import axios from 'axios';
const instance = axios.create({
baseURL: process.env.REACT_APP_BASE_URL
// headers: { 'X-Custom-Header': 'foobar' }
});
// Add a request interceptor
instance.interceptors.request.use(
(config) => {
// Do something before request is sent
return config;
},
(error) => {
// Do something with request error
return Promise.reject(error);
}
);
// Add a response interceptor
instance.interceptors.response.use(
(response) => {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
},
(error) => {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
}
);
export default instance;
2- i use that service to create a function for api call.
in here i can add a new AbortController for later use in useEffect.
import axios from 'services/request';
export function getMarket(type, callBack) {
const controller = new AbortController();
axios
.get(`https://dev.zh1.app/api/market/map?type=${type}`, {
signal: controller.signal
})
.then((res) => {
callBack(true, res.data);
})
.catch((res) => {
callBack(false, res.response);
});
return controller;
}
export default {
getMarket
};
3- in the hooks folder i created a hook called useApi. the controller from step 2 used in here. if you check the link above you can see the author add request function because you may have some props to pass to api call. i think it is valid but ugly. so i decided to create a closure for useApi to pass any params i want to the Axios in step 2.
import { useEffect, useState } from 'react';
// in useStrict mode useEffect call twice but will not in production
export default function useApi(apiFunc) {
return function useApiCall(...params) {
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const apiCall = useCallback(() => {
setLoading(true);
// controller is new AbortController which set each api function
const controller = apiFunc(...params, (ok, data) => {
setLoading(false);
if (ok) {
setResult(data);
} else {
setError(data.message);
}
});
return () => {
controller.abort();
};
}, []);
useEffect(() => {
apiCall();
}, []);
return {result, loading, error, [apiFunc.name]: apiCall};
};
}
4- finally in my react component
import { IconButton } from '#mui/material';
import useApi from '#share/hooks/useApi';
import { Refresh } from '#share/icons';
import { getCaptcha as CaptchaApi } from 'api/oauth/captcha';
import CaptchaStyle from './style';
export default function Captcha() {
const { result: captcha, getCaptcha } = useApi(CaptchaApi)();
return (
<CaptchaStyle>
<img src={`data:image/png;base64,${captcha?.base64}`} alt="captcha" />
<IconButton onClick={getCaptcha}>
<Refresh />
</IconButton>
</CaptchaStyle>
);
}
i think this approach i quite good and if you dont need to pass any props just call useApi([yourfunction])() with empty function.
and you can have access to the function inside of useApi if you need to call it again.
It totally depends on your project. If your project relies more on the function component then go ahead and use the first approach.
If you use classes for the majority of your components go for the second approach.
I generally use the first approach, it's easy and avoids this altogether. Also, it's easy to target multiple instances.
// Ajax.js file
import axios from "axios";
export function updateData=(body,callback){
le url= 'your api to call'
axios
.put(url, body)
.then((response) => response.data)
.then((res) => {
callback(res);
})
.catch((error) => {
console.log(error);
callback('error occurred');
});
}
// app.js file
import {updateData} from './ajax.js'
//Place your code where you need
updateData(yourBodyToPass,res=>{
//Stuff after the response
)
Note:- pass your data as first argument and get response of api from second
I get this error in console.
xhr.js:178 GET https://www.googleapis.com/youtube/v3/search?q=river 403
I created a separate component for later use of this api:
const KEY = "mykeyas23d2sdffa12sasd12dfasdfasdfasdf";
export default axios.create({
baseURL: "https://www.googleapis.com/youtube/v3",
params: {
part: "snippet",
maxResults: 5,
key: KEY,
},
});
then in App.js called:
import youtube from "../apis/youtube";
class App extends React.Component {
handleQuery = (term) => {
youtube.get("/search", {
params: {
q: term,
},
});
};
...
}
However, when I put them together. Like:
class App extends React.Component {
handleQuery = (term) => {
console.log(term);
axios.get("https://www.googleapis.com/youtube/v3/search", {
params: {
part: "snippet",
maxResults: 5,
key: "mykeyas23d2sdffa12sasd12dfasdfasdfasdf",
q: term,
},
});
};
render() { ... }
}
export default App;
This above code works fine, but first doesn't. How do I solve the error with that separate component?
Unfortunately, axios made some changes from version 0.18x to 0.19x and made a change on the feature that enables to send an instance of axios with axios.create().
This means that you cannot export your default axios config and import it, at least until feature releases. For you, this resulted in a 403 error, because axios did not have prior knowledge of the config defined in ../api/youtube due to the modifications I mentioned.
This is according to this github issue: axios.create() can't not handle params
A solution to your problem would be to define your config file in, say api/config as:
export const config = {
baseURL: 'https://www.googleapis.com/youtube/v3',
params: {
part: 'snippet',
maxResults: 5,
key: YOUR_API_KEY,
},
};
And use it in your App.js as:
import React from 'react';
import axios from 'axios';
import { config } from './apis/config';
class App extends React.Component {
handleQuery = async (term) => {
console.log(term);
config['params'] = { ...config['params'], q: term };
const response = await axios.get('/search', config);
console.log(response);
};
return (...)
}
*What I did was to concatenate to the imported config the q value. This is done easily in javascript using {...config, key:value}. The ... mean: copy everything from config as it was before.
EDIT
Your config object should look like this (after setting the q parameter):