Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the alternative to use hooks inside non React component?

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?

like image 223
warheat1990 Avatar asked Sep 27 '21 02:09

warheat1990


People also ask

Can Hooks be used outside of React components?

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.

Can I use Hooks in class component?

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.

In which of the following cases would you use Hooks instead of classes?

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.

Can we use Hooks in functional component?

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


1 Answers

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";

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.

like image 98
Emile Bergeron Avatar answered Oct 17 '22 03:10

Emile Bergeron