Kikkomann
Kikkomann

Reputation: 416

Access React context in an API service

In my React application I use the context API to store the user information through the useContext hook:

const AuthContext = createContext<AuthContextType>(null!);

const useAuth = () => useContext(AuthContext);

function AuthProvider({ children }: { children: ReactNode }) {
    const [user, setUser] = useState<User>();

    // Implementations of values

    const value = useMemo(() => ({ user, login, logout }), [user]);

    return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export { AuthProvider, useAuth };

Accessing the auth information works all fine and dandy in the components:

export default function CoolComponent() {
  const auth = useAuth();
  if (auth.user) {
    // Do something
  }
  return <div>Hello {auth.user}</div>;
}

The thing is that my jwt-token is stored in the user object and I need it for my API calls in my service, but hooks are not allowed outside functional components. Can I circumvent this in a clever way? Some things that I can think of is to pass the token on every call to the service (not very DRY) or save the token in localStorage and then retrieve it from there in the service, but it seems unnecessary to store the same information in two different places?

Update:

Now with the service code:

const baseUrl = environment.apiUrl;

function getToken() {
  // This is what I would like to get some help with
}

const headers = {
  ...(getToken() && { Authorization: `Bearer ${getToken()}` }),
  "Content-Type": "application/json",
};

function getAllProjects(): Promise<IProject[]> {
  return fetch(`${baseUrl}projects`, {
    headers,
  }).then((response) => response.json());
}

function createProject(project: CreateProjectDTO): Promise<IProject> {
  return fetch(`${baseUrl}projects`, {
    method: "POST",
    headers,
    body: JSON.stringify(project),
  }).then((response) => response.json());
}

// + many more

export { getAllProjects, createProject };

Calling the service in a component:

useEffect(() => {
  const fetchProjects = async () => {
    setIsLoading(true);
    try {
      const allProjects = await getAllProjects();
      setProjects(allProjects);
    } catch (error) {
      // Handle error
    } finally {
      setIsLoading(false);
    }
  };
  fetchProjects();
}, []);

Upvotes: 0

Views: 2088

Answers (1)

Nigar Jafar
Nigar Jafar

Reputation: 144

The React documentation says that you cannot call hooks inside JavaScript functions.

What can you do?

Use custom hooks. rename functions as useCreateProject and return your function. Then you will be able to call useAuth inside your custom hook:

 const useCreateProject =() =>{   
      const {user} = useAuth();
      
      function createProject(project: CreateProjectDTO): Promise<IProject> {
         return fetch(`${baseUrl}projects`, {
         method: "POST",
         headers,
         body: JSON.stringify(project),
        }).then((response) => response.json()); 
      }
 
      return createProject
 }

Then call it like this:

 const createProject = useCreateProject()
 useEffect(() => {
   const create = async () => {
     setIsLoading(true);
     try {
       await createProject()
     } catch (error) {
       // Handle error
     } finally {
       setIsLoading(false);
     }
   };
   create();
 }, []);

But my advice is to store the token on localStorage or in cookies. Context data will be lost when user refreshes page. However, if that is not case for you, you can continue using context.

Upvotes: 1

Related Questions