While creating a reusable, importable function for making API calls I ran into the issue of not being able to access the Context API to get the auth token.
Importable Function:
import React, { useContext } from 'react';
import { Context } from '../Context';
import axios from 'axios';
const FetchDataFunction = (method, endpoint, params) => {
const [context] = useContext(Context); // the line throwing the error
const api = axios.create({
baseURL: `...`,
headers: {
'Authorization': 'Bearer 11111111111111111', //need this from API Context
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
...make api request...
}
Component trying to use the function
import React, { useContext } from 'react';
import FetchDataFunctionfrom '../../../../FetchDataFunction';
import { Context } from '../../../../Context';
const ComponentName = () => {
const [context] = useContext(Context); //works!
useEffect(() => {
async function fetchData() {
var result = await FetchDataFunction('get','endpoint', {params: {...}});
}
fetchData();
},[]);
}
Error:
Unhandled Rejection (Error): Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See link for tips about how to debug and fix this problem.
I need access to the context api to get auth tokens as well be able to update the context api when auth tokens expire. Thus passing the context to the function is not an option. React/Redux is complex and seems like it will be replaced by Context.
Anyone have a solution to this problem?
context is meant for functional Components and not functions. Why not read context value and pass in your function as parameter?
I used a custom hook that would fetch the data.
See here: https://reactjs.org/docs/hooks-custom.html
const useApi = (method, endpoint, params) => {
const [response, setResponse] = useState(null);
const [context, setContext] = useContext(Context);
const api = axios.create({
baseURL: `URL`,
headers: {
'Authorization': 'Bearer ' + context.user.bearer,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
});
useEffect(() => {
const fetchData = async () => {
//Just did a GET for this, but you can easily change this to be more universal
const res = await api.get(endpoint,params);
setResponse(res);
};
fetchData();
}, [] );
return response;
}
Then in the component file:
import { useApi } from './../../../../Utilities/Hooks/use-api.js';
/* ..... */
const something= useApi(
'get',
'something/',
{params: {foo: bar}},
);
/* .... */
useEffect( () => {
if (something){
// Do something with the API call data you get back here
}
},[something]);
Related
I have to add a token that is created upon login and put in cookies into my request data to every fetch request I make. I'm currently doing it by using a custom hook that will add it every time so I don't have to add it multiple times. Is there an easier way? Maybe with axios?
Here is my custom hook:
import { useQuery as useBaseQuery } from 'react-query';
import axios from 'axios';
const fetcher = async (url, options) => {
const token = Cookies.get('TOKEN');
const { data } = await axios.get(url, {
data: { ...options, 'TOKEN': token },
});
return data;
};
const useQuery = (queryKey, query, options) => {
return useBaseQuery(queryKey, async () => {
return await fetcher(query, options);
});
};
export default useQuery;
and is used like this:
import useQuery from './useBaseQuery';
const requestData = {
method: 'GET',
path: pathToUrl,
};
export default function useGetActionAlerts() {
return useQuery('actionAlerts', '/bin/user', requestData);
}
You need to use interceptor, from documentation
You can intercept requests or responses before they are handled by then or catch.
https://axios-http.com/docs/interceptors
So i have this react functional component :
import { createContext, useContext, useState } from 'react';
const GeneralContext = createContext();
export function GeneralContextWrapper({ children }) {
const [userSessionExpired, setUserSessionExpired] = useState(false);
return (
<GeneralContext.Provider
value={{
userSessionExpired,
setUserSessionExpired,
}}
>
{children}
</GeneralContext.Provider>
);
}
export function useGeneralContext() {
return useContext(GeneralContext);
}
I have to change the state of userSessionExpired in a normal function so i do :
const checkToken = (data) => {
const { userSessionExpired, setUserSessionExpired } = useGeneralContext();
if (data.status === 'fail' && data.message === 'Sessione scaduta') {
window.location.href = '/auth/login?session=expired';
localStorage.removeItem('jwt');
}
return data;
};
And i got eslint warning :
React Hook "useGeneralContext" is called in function "checkToken" that
is neither a React function component nor a custom React Hook
function. React component names must start with an uppercase letter.
where i call the checktoken function example in a fetch call :
export const call = async (url, token, data) => {
const dataReturned = await fetch(url, {
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(data),
})
.then((response) => {
return response.json();
})
.catch((err) => {
return { status: 'fail', message: 'API CALL ERROR', error: err.message };
});
return checkToken(dataReturned);
};
I would like to know if i can ignore the warning or not?
It's unclear how your checkToken is ever called, but you surely shouldn't be using any hooks from within an async function. In order for hooks to work properly, they have to be called in the same order every time the component renders, and if a call is originating from within an async function, it's almost guaranteed that's not going to happen.
The warning you're seeing is good advice, and you should heed it. Call your hooks within your component (or within other hooks that you create), and then if you have a function that needs the result of those hooks (in your case, a context), then pass that into the function.
I am using DRF for creating the api.. I am able to fetch the data using axios, but it returns undefined the first time and hence, when I use useState, it gets set as undefined..
ItemDetail.js:
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const ItemDetail = () => {
const [detail, setDetail] = useState('')
const [id, setId] = useState('')
const RecipeDetail = async () => {
const res = await axios({
method: 'get',
url: `http://127.0.0.1:8000/api/recipe-detail/1`,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
"Access-Control-Allow-Origin": "*",
}
})
setDetail(res.data)
}
useEffect(() => {
RecipeDetail()
}, [id])
console.log(`Hi ${detail}`)
return (
<div>
Hi
</div>
)
}
export default ItemDetail
So why is the API returning undefined for the first time?
I read few answers regarding the use of async/await which I have.. Also, when I console.log(detail), it logs it multiple times.. Why is that so?
As u can see in the image, it logs multiple times..
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const ItemDetail = () => {
const [detail, setDetail] = useState('');
const RecipeDetail = async () => {
const res = await axios({
method: 'get',
url: 'http://127.0.0.1:8000/api/recipe-detail/1',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Access-Control-Allow-Origin': '*',
},
});
setDetail(res.data);
};
useEffect(() => {
RecipeDetail();
}, [detail]);
console.log(`Hi ${detail}`);
return (
<div>
{detail ? 'Hi' : 'Loading ... '}
</div>
);
};
export default ItemDetail;
You are trying to access or log the detail before it is set ,means that useEffect will be called after your content is rendered so console.log(Hi ${detail}); ran first and undefined was logged , later useEffect ran and RecipeDetail(); data received state changed as you called setState. and rerender occured again and this time you received value.
When it was first rendered, no API response was received yet.
and then, when second rendered, API resonse was receiced.
I have set up Auth0 with React by following their Quickstart tutorial.
Basically my React app is wrapped around their Context Provider and I have access to the useAuth0 hook in any of my components.
This is how I would make a request to my API:
const TestComponent = () => {
const { getTokenSilently } = useAuth0();
const getObjectsFromAPI = async () => {
const token = await getTokenSilently();
const axiosConfig = {
headers: {
Authorization: "Bearer " + token
}
};
const response = await axios.get(
"/api/objects/",
axiosConfig
);
// ... do something with the response
};
return ... removed code for brevity
};
Is there a way to make the requests without having to write the token and axiosConfig on each request?
I know that I can initialize a new axios instance with a config, but I cannot use the useAuth0 hook outside the Context Provider.
but I cannot use the useAuth0 hook outside the Context Provider.
Right, not sure how you can avoid token generation per request but you can save the axios config part by passing the token to a shared axios instance, something like:
http.js
const instance = axios.create({
// your config
});
export const authorized = (token) => {
instance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
return instance;
}
And in your component:
import http from '/path/to/above/http.js';
const TestComponent = () => {
const { getTokenSilently } = useAuth0();
const getObjectsFromAPI = async () => {
const token = await getTokenSilently();
const response = await http
.authorized(token)
.get('/api/objects/');
// ...
};
};
I'm trying to write a custom fetch hook, but I guess im missing something.
import React, { useContext } from 'react';
import { Context } from "../components/context";
const fetchHook = async(url: string, bearer: string, method: string, body: any ) => {
const { global } = useContext(Context) as {global: any};
let headers = {'cache-control': 'no-cache', 'Content-Type': 'application/json' };
if (bearer) headers = {...headers, ...{'Authorization': bearer}}
if (method === 'GET') return await fetch(global.apiUrl + url, {method, headers});
else return await fetch(global.apiUrl + url, {method, headers, body});
}
export { fetchHook }
The error im getting is Line 5: React Hook "useContext" is called in function "fetchHook" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks
UPDATE:
import React, { useContext } from 'react';
import { Context } from "../components/context";
const useFetch = (url: string, bearer: string, method: string, body: any) => {
const { global } = useContext(Context) as {global: any};
let headers = {'cache-control': 'no-cache', 'Content-Type': 'application/json' };
if (bearer) headers = {...headers, ...{'Authorization': bearer}}
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
const apiUrl = global.apiUrl;
React.useEffect(() => {
const fetchData = async () => {
try {
let res;
if (method === 'GET') res = await fetch(apiUrl + url, {method, headers});
else res = await fetch(global.apiUrl + url, {method, headers, body});
setResponse(await res.json());
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
export { useFetch }
The only warning I get now I about a missing dependency warning but I'm not sure how to fix it. Should I be passing all the dependencies into the square brackets of useEffect()?? I'm just not sure?
Line 27: React Hook React.useEffect has missing dependencies: 'apiUrl', 'body', 'global.apiUrl', 'headers', 'method', and 'url'. Either include them or remove the dependency array
You are getting this warning because according to the Rules of hooks, a custom hook name must start with use.
As mentioned the docs of custom hooks
A custom Hook is a JavaScript function whose name starts with ”use”
and that may call other Hooks.
You won't receive the error if you rename the hook to
const useFetchHook = async(url: string, bearer: string, method: string, body: any ) => {
const { global } = useContext(Context) as {global: any};
let headers = {'cache-control': 'no-cache', 'Content-Type': 'application/json' };
if (bearer) headers = {...headers, ...{'Authorization': bearer}}
if (method === 'GET') return await fetch(global.apiUrl + url, {method, headers});
else return await fetch(global.apiUrl + url, {method, headers, body});
}
export { useFetchHook }
Also one thing you must keep in mind is that if you execute async code within the custom hook directly, it will be executed on every render. A better way is to maintain state and fetch the data within useEffect and update the state when the data is received.