0

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();
}, []);

6
  • What do your API calls look like? My initial instinct would be to wrap your API in calls in a re-usable hook that bakes in the token by reading from the context. Then instead of calling the API service directly you call it through a callback return from a re-usable React hook. Commented May 15, 2022 at 22:10
  • I agree, we need to see the API service code to help make any improvements there. Commented May 15, 2022 at 22:14
  • I have now updated my answer. I have not much experience in making a custom hook, but I will look into it. The guides on API calls usually have the fetch or at least the URL in the component, but I would prefer to have all API-specific data collected in the service, so I just have to call something like getAllProjects. Is either method a better practice? Commented May 15, 2022 at 22:33
  • How opposed would you be to creating/exposing your API functions in your AuthContext? It'd be trivial to create functions there that can directly access the auth state and JWT token value. Commented May 15, 2022 at 22:43
  • @DrewReese Well that is a good suggestion. I will do that. Commented May 16, 2022 at 8:57

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.