What is proper way to use react hooks when calling javascript functions?

Viewed 329

I'm just learning how hooks work in larger applications. I'm trying to figure out what the correct pattern is here. I typically wrap my api calls in their own files. For instance, I might call out to get a list of customers in a file Customers.Api.js:

import axios from 'axios';

const client = await getApiClient();
const reply = await client.get(`${baseUrl}/customers`);
return reply;

The job of getApiClient() is to get an axios instance that already has the Authorization header added. I am getting this from Auth0, but that part doesn't matter as I am really looking for the pattern here and not specifics to Auth0, could be any hook:

// Engine.Api.js
// Inside an axios interceptor called from getApiClient()
const { getAccessTokenSilently } = useAuth0(); // <-- this is not allowed in a plain js func

const token = await getAccessTokenSilently({}); 
request.headers.Authorization = `Bearer ${token}`;
...
return request

Since my token comes from a hook, how should I be setting this up?

I don't want to do this every time:

const { getAccessTokenSilently } = useAuth0();
const token = await getAccessTokenSilently({});
const client = await getApiClient();
const reply = await client.get(`${baseUrl}/customers`,{
  headers: `Authorization: Bearer ${token}`,
})

I would prefer some way to do this (or something similar):

const customers = await getCustomers();

getCustomers() would live in Customers.Api.js and would have something like:

const client = await getApiClient(); // <-- this would use interceptor to add bearer
return await client.get(`${baseUrl}/customers`);

Final Solution

After looking at what Andrey had to say, I finally got to where I needed to be. Here is what I ended up doing:

const axiosClient = axios.create();

export function useApiClient() {
  const { getAccessTokenSilently, isLoading } = useAuth0();
  const [state, setState] = useState({
    loading: true,
    authHeader: null,
    token: '',
    client: null,
  });

  useEffect(() => {
    (async () => {
      if (!isLoading) {
        if (state.token === '') {
          const apiToken = await getAccessTokenSilently({
            audience: 'https://example.com/',
            scope: 'read:current_user',
          });
          setState({ ...state, authHeader: { Authorization: `Bearer ${apiToken}` }, token: apiToken });
    }

    if (state.token && !state.client && state.loading) {
      setState({ loading: false, client: axiosClient });
    }
  }
})();
}, [getAccessTokenSilently, state, isLoading]);
  return { ...state };

}

I also modified it a lot after that as this was not really my true use case, but it identified what I was not understanding. Thanks Andrey!!!!

1 Answers

This is usually solved by creating another hook that would wrap all necessary logic inside.

Auth0 documentation actually has an example of that: https://github.com/auth0/auth0-react/blob/master/EXAMPLES.md#4-create-a-useapi-hook-for-accessing-protected-apis-with-an-access-token

In your case you can do smth like this:

Customers.Api.js:

export function useCustomers() {
  const [state, setState] = useState({ customers: [] });
  const { getAccessTokenSilently } = useAuth0();
  useEffect(() => {
    (async () => {
      const token = await getAccessTokenSilently({});
      const client = await getApiClient();
      const reply = await client.get(`${baseUrl}/customers`,{
        headers: `Authorization: Bearer ${token}`,
      })
      setState({ customers: reply.data });
    })
  });
  return state;
}

(not tested, might not actually work, but hopefully the idea is clear)

in a component:

const { customers } = useCustomers();

Next step would be to generalize useCustomers hook into smth like useApi and inside useCustomers simply call useApi.

Also you might find official Building Your Own Hook documentation helpful, it explains most common gotchas.

Related