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 !
Related
I am trying to fetch data from the backend and then render a custom component with props as the data fetched from the backend. Here's a snippet:
import { useEffect } from 'react';
import axios from 'axios';
import Card from './Card';
export default function DesignList() {
useEffect(() => {
async function populate() {
const response = await axios({
method: 'POST',
baseURL: 'http://localhost:3001',
url: '/recommend',
headers: { 'Content-Type': 'application/json' },
});
response.data.forEach(recommendation => {
document.getElementById('design-list').append(<Card
title={recommendation.title}
});
}
populate();
})
return (
<div id='design-list' />
)
}
I also tried to use React.createElement(<Card .../>), pushed it in an array and tried to render it but I got the same output as described below.
This gives an output on the screen but not as a HTML component. The output for each component is [object Object]
output
How do I render the components dynamically after fetching the props data from backend?
Use state for showing recommendations, not dom append.
import { useEffect, useState } from "react";
import axios from "axios";
import Card from "./Card";
export default function DesignList() {
const [recommendations, setRecommendations] = useState([]);
useEffect(() => {
async function populate() {
const response = await axios({
method: "POST",
baseURL: "http://localhost:3001",
url: "/recommend",
headers: { "Content-Type": "application/json" },
});
setRecommendations(response.data);
}
populate();
});
return (
<div id="design-list">
{recommendations.map((item) => (
<Card title={item.title} />
))}
</div>
);
}
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 have a React app that is making calls to an API. I have a Client component to handle the calls, and the Components can access it like this (this example is in the componentDidMount function of the Home page, where I want to get a list of all this user's items):
componentDidMount() {
let userId= this.context.userId;
var url = "items/getAllMyItems/" + userId;
Client.fetchData(url, data => {
this.setState({items: data});
});
}
The current setup has no security (just for testing purposes) and the Client is defined like this (this is index.js):
function fetchData(fetchPath, cb) {
return fetch(`https://api.url/${fetchPath}`, {accept: "application/json"})
.then(cb);
}
(there are a couple of other functions which check the results etc, but I've left them out for brevity).
Now, my app connects to Firebase for handling authentication. I have A Firebase component which has 3 files:
firebase.js:
import app from 'firebase/app';
import 'firebase/auth';
const config = {
apiKey: /* etc */,
};
class Firebase {
constructor() {
app.initializeApp(config);
this.auth = app.auth();
}
// *** Auth API ***
doSignInWithEmailAndPassword = (email, password) =>
this.auth.signInWithEmailAndPassword(email, password);
doSignOut = () => this.auth.signOut();
}
export default Firebase;
context.js:
import React from 'react';
const FirebaseContext = React.createContext(null);
export const withFirebase = Component => props => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
export default FirebaseContext;
index.js:
import FirebaseContext, { withFirebase } from './context';
import Firebase from './firebase';
export default Firebase;
export { FirebaseContext, withFirebase };
We're now implementing backend security, and I need to pass the Firebase token to the API when making calls. I can't figure out how to do it properly.
I know I need to call
firebase.auth().currentUser.getIdToken(true).then(function(idToken) {
// API call with Authorization: Bearer `idToken`
}).catch(function(error) {
// Handle error
});
so I figured that Client/index.js would need to change to something like:
import react from 'react';
import { FirebaseContext } from '../Firebase';
function fetchData(fetchPath, cb) {
<FirebaseContext.Consumer>
{firebase => {
firebase.auth().currentUser.getIdToken(true)
.then(function(idToken) {
// API call with Authorization: Bearer `idToken`
return fetch(`https://api.url/${fetchPath}`, {accept: "application/json"})
.then(cb);
}).catch(function(error) {
// Handle error
});
}}
</FirebaseContext.Consumer>
}
but if I do this I get the error "Expected an assignment or function call but instead saw the expression". I realize this is because it's expecting me to return a component, but I don't want to do that as there's nothing to return. I also tried using useContext, and changing fetchData to:
const Client = () => {
const firebase = useContext(FirebaseContext);
firebase.auth().currentUser.getIdToken(true)
.then(function(idToken) {
// API call with Authorization: Bearer `idToken`
fetch(`https://api.url/${fetchPath}`, {accept: "application/json"})
.then(cb);
}).catch(function(error) {
// Handle error
});
}
but I got an error about an Invalid Hook Call.
Can anyone point me in the right direction?
The code you have to get the ID token looks fine to me.
How to pass it to the API depends on what that API expects, but since you mention ```Authorization: Bearer idToken `` that would typically look like this:
fetch(`https://api.url/${fetchPath}`, {
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer ' + idToken
}
})
Using NativeScript vue. I have put axios in its own file, where I can add interceptors etc (in this case to handle a JWT refresh token). In my vuex store (ccStore) I have stored the authToken, and refreshToken. I'm using vuex persist.
import axios from 'axios'
// abridged ...
import ccStore from './store';
const DEV_BASE_URL = ccStore.getters.getBaseURL // local ip injected by webpack
const PROD_BASE_URL = ''
axios.defaults.baseURL = (TNS_ENV === 'production') ? PROD_BASE_URL : DEV_BASE_URL
axios.interceptors.request.use( function (config) {
let token = ''
if(config.url.includes('/auth/refresh')) { //use the refresh token for refresh endpoint
token = ccStore.getters.getRefreshToken;
} else { //otherwise use the access token
token = ccStore.getters.getAuthToken;
}
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
function (error) {
console.log(error)
return Promise.reject(error)
}
)
axios.interceptors.response.use(
function(response) {
console.log(response)
return response
},
function(error) {
const originalRequest = error.config
if(error.response && error.response.status === 401) {
if (originalRequest.url.includes('/auth/refresh')) { //the original request was to refresh so we must log out
return ccStore.dispatch('logout')
} else {
return ccStore.dispatch('refreshToken').then(response => { //try refreshing before logging out
return axios(originalRequest)
})
}
} else {
console.log(error)
return Promise.reject(error)
}
}
)
export default axios;
In my app.js file, I import this modified axios, and assign it to
Vue.prototype.$http = axios;
I did this so that the same instance of axios with interceptors is available in every component [ I ran into some problems doing a refactor last night - circular references when including my modified axios in the mounted hook of each component... but sticking it globally seems to work ]
However, in my app.js file I am also calling ccStore so that I can attach it to my vue instance... Am I doing this right? Is the same instance of ccStore being referenced in both app.js and axios?
Also - to bend the mind further, I have some actions within my vuex store for which I need axios... so I am also having to include axios within my vuex store file - yet axios already includes my vues store...
so...
app.js imports store and axios,
store imports axios,
axios imports store
Is this not circular too?
I don't know if it can be helpful, but I use to initialize a custom Axios instance.
scripts/axios-config.js
import axios from 'axios';
import store from '../store';
import Vue from 'nativescript-vue';
export default {
endpoint: "https://myendpoint.com",
requestInterceptor: config => {
config.headers.Authorization = store.getters.getToken;
return config;
},
init() {
Vue.prototype.$http = axios.create({ baseURL: this.endpoint });
Vue.prototype.$http.interceptors.request.use(this.requestInterceptor);
}
}
app.js
import Vue from 'nativescript-vue';
import store from './store';
import axiosConfig from './scripts/axios-config';
import router from './router'; // custom router
axiosConfig.init();
// code
new Vue({
render: h => h('frame', [h(router['login'])]),
store
}).$start();
No store.js code because I simply import nativescript-vue, vuex and (if needed) axiosConfig object.
I've never had circular problems this way, hope it helps
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
}