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?
You can not use hooks outside a component function, it is simply how they work. But, you can make a composition of hooks. React relies on an amount and order of how hooks appear in the component function.
You can't use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component.
Hooks allow you to use local state and other React features without writing a class. Hooks are special functions that let you “hook onto” React state and lifecycle features inside function components. Important: React internally can't keep track of hooks that run out of order.
Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don't work inside classes — they let you use React without classes. (We don't recommend rewriting your existing components overnight but you can start using Hooks in the new ones if you'd like.)
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.
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>
}
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";
const UserServiceContext = React.createContext(null);
// Convenience hook
export const useUserService = () => useContext(UserServiceContext);
// Local axios instance
const axiosInstance = axios.create({
baseURL: 'https://<url>', // set the base URL once here
});
const userServiceInstance = new UserService(axiosInstance);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With