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.
Related
This should work ->
import { useEffect } from 'react'
import { useSelector, useDispatch } from "react-redux";
import CheckLoginStatus from "../../utils/CheckLoginStatus";
import ProductDetailDisplay from '../../components/product/ProductDetailDisplay'
// using #reduxjs/toolkit as Redux state / store thing
import { selectProduct, getProductById } from '../products/productsSlice'
function handleAddToCart(){
alert('handleAddToCart')
}
export default function ProductDetail ( props ) {
let { productId } = props
CheckLoginStatus()
const dispatch = useDispatch()
const productData = useSelector(selectProduct)
useEffect(() => {
console.log('useEffect()') // <<< 'useEffect()' appears in console
dispatch(getProductById(productId)); // <<< it looks like it's never called
},[])
return <ProductDetailDisplay data={productData} handleAddToCart={ handleAddToCart }/>
}
However, the 'dispatch(getProductById(productId))' inside of useEffect doesn't happen. (edit) However, the console.log('useEffect()') line does execute, and the message appears in the console.
I've been staring at this for an entire day, trying out all of the combinations of solutions I can think of, and have nothing.
Can someone please let me know where I'm missing the point with this code? It should work, and a single product record should be available to ProductDetailDisplay. But it looks like 'getProductById(productId))' never gets called.
Thanks in advance.
(edit) krirkrirk, here is the code for getProductById ->
export const getProductById = createAsyncThunk (
'products/getProductById',
async (product_id) => {
let theApiUrl = API_BASE_URL + `/api/v1/product/${product_id}`
let authToken = useSelector(selectJwtToken)
console.log('getProductById', product_id)
try {
const response = await fetch(
theApiUrl,
{
method: 'GET',
headers: {
'Accept': "*/*",
'Content-Type': "application/json",
'Authorization': `Bearer ${authToken}`,
},
credentials: 'include',
}
)
let data = await response.json();
if (response.status === 200) {
// console.log('getProductById 200', data[0])
console.log('productById ', data)
return data[0] // FIXME: shouldn't have to juggle arrays when assigning to state...
} else if (response.status === 401) {
console.log('getProductById get request auth fail.')
// return thunkAPI.rejectWithValue(data)
}
} catch (e) {
console.log("Error: ", e.response.data)
// return thunkAPI.rejectWithValue(e.response.data)
}
}
) // end getProductById
I was trying to create a custom Hooks for handling input HTTP request from any component by simply calling the useCustomHooks but its getting failed and error is
Can not use keyword 'await' outside an async function
All i made is a handler that triggers http request custom component method
import { useState } from 'react';
import axios from "axios";
const useHttpReqHandler = () => {
const [result, setResult] = useState();
const apiMethod = async ({url , data , method}) => {
let options = {
method,
url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
data
};
let response = await axios(options);
const UpdatedData = await response.data;
console.log(UpdatedData)
setResult(UpdatedData);
}
return [result, apiMethod];
};
export default useHttpReqHandler;
Now i can use this hook in my code and on any event handler just call callAPI returned from the hook like this
const MyFunc = () => {
const [apiResult, apiMethod] = useHttpReqHandler();
const captchValidation = () => {
const x = result.toString();;
const y = inputValue.toString();;
if ( x === y) {
apiMethod({url: 'some url here', data: {"email": email}, method: 'post'});
alert("success")
}
else {
alert("fail")
}
}
Is is a correct approch ? as i am beginner in Reactjs
Here is a working version:
useHttpReqHandler.jsx
import { useState } from 'react';
import axios from "axios";
const useHttpReqHandler = () => {
const [apiResult, setApiResult] = useState();
const apiMethod = async ({url , data , method}) => {
let options = {
method,
url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
data
};
let response = await axios(options);
let responseOK = response && response.status === 200 && response.statusText === 'OK';
if (responseOK) {
const data = await response.data;
console.log(data)
setApiResult(data);
}
}
return [apiResult, apiMethod];
};
export default useHttpReqHandler;
What's important here:
await is called inside an async function (apiMethod)
The result is stored in a local state (apiResult)
The function returns an array [apiResult, apiMethod]
How to use it:
const [apiResult, apiMethod] = useHttpReqHandler();
apiMethod({url: 'some url here', data: {"email": email}, method: 'post'});
Render the result:
return {apiResult};
In my opinion, it is better to use .then with Axios. and try to create for each method different functions "Get/Post...", why because in the GET method you need to useEffect, but it can not be the same case in POST method. in GET method useHttpReqHandler.js
import { useEffect, useState } from "react";
import axios from "axios";
// GET DATA
const useHttpReqHandler = (url) => {
const [httpData, setHttpData] = useState();
useEffect(() => {
axios
.get(url)
.then((axiosData) => {
// Axios DATA object
setHttpData(axiosData.data);
// you can check what is in the object by console.log(axiosData);
// also you can change the state, call functions...
})
.catch((error) => {
console.log(error);
});
}, []);
return httpData;
};
export default useHttpReqHandler;
in your main file
import useHttpReqHandler from "...."
const MyFunc = () => {
const getData = useHttpReqHandler("URL");
return (
<div>
...
</div>
)
}
I hope it helps
the same thing will be with POSt, PUT, DELETE ... you will create functions for each method that will handle the Http req
I have an API hook called useAPICall that has a callback call. This callback checks if a token stored in a reactn variable called auth is expired, refreshes it if necessary, then calls the fetch function.
I call it in my component like this:
const [api] = useAPICall();
useEffect(() => {
api.call('/api/settings/mine/').then(data => {
// do stuff here
});
}, []);
And it does work. It goes through the authentication flow and calls the API. But if I have useAPICall is multiple components that all try to call the API around the same time (such as a cold page load), then each instance of it calls the refresh token method because it's expired.
The auth info (access/refresh tokens) are stored in a reactn global variable auth such as below, inside the useAPICall.js hook
import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
import {useDispatch, useGlobal} from 'reactn';
export function useAPICall() {
const [auth, setAuth] = useGlobal('auth');
const authRefreshSuccess = useDispatch('authRefreshSuccess');
async function refreshToken() {
console.log('Refreshing access token...');
const authResponse = await fetch('/api/auth/token/refresh/', {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({refresh: auth.refresh.token}),
headers: {
'Content-Type': 'application/json',
},
});
if (authResponse.ok) {
const authToken = await authResponse.json();
await authRefreshSuccess(authToken);
return authToken.access;
}
}
function isTokenExpired() {
if (localAuth.access)
return auth.access.exp <= Math.floor(Date.now() / 1000);
else
return false;
}
const call = useCallback(async (endpoint, options={headers: {}}) => {
console.log('performing api call');
token = undefined;
if (isTokenExpired())
token = await refreshToken();
else
token = localAuth.access.token;
const res = await fetch(endpoint, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
}
});
if (!res.ok)
throw await res.json();
return res.json();
}, []);
const anonCall = useCallback(async (endpoint, options={}}) => {
const res = await fetch(endpoint, options);
if (!res.ok)
throw await res.json();
return res.json();
}, []);
const api = useMemo(
() => ({
call,
anonCall,
}),
[call, anonCall,]
);
return [api]
}
How can I prevent them from firing off the refresh method multiple times?
If there's a better way (without redux) to have a universal API flow (where any API call would first check access token and refresh if necessary), then I'm willing to listen.
I managed to do this by storing a promise in a global variable.
let refreshPromise = null;
export function useAuthentication() {
async function getBearer() {
if (isExpired(jwt)) {
if (refreshPromise == null) {
refreshPromise = refresh().then((jwt) => {
refreshPromise = null;
return jwt;
});
}
await refreshPromise;
}
let authData = getAuthData();
if (authData && authData.accessToken) {
return `Bearer ${authData.accessToken}`;
}
return null;
}
const AuthenticationService = {
getBearer,
...
};
return AuthenticationService;
}
Hope this helps !
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]);
My first attempt at a custom hook is now looping like crazy, I don't fully understand why do I need to test to see if it has something saved in response or error then make the call if it has not?
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(apiUrl + url, {method, headers, body});
setResponse(await res.json());
} catch (error) {
setError(error);
}
};
fetchData();
}, [apiUrl, body, headers, method, url]);
return { response, error };
};
export { useFetch }
I'm calling it with
import { useFetch } from '../hooks/fetch';
const res = useFetch('http://api.domain.com', '', 'GET', '')
console.log(res);
Each time you unconditionally change state in useEffects it will loop endlessly because it is calling after each state change, you change state again and again useEffects is calling and so on...
There should be some flag probably, and you fetch data only if flag is not true
const [isLoaded, setIsLoaded] = React.useState(false);
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
const apiUrl = global.apiUrl;
React.useEffect(() => {
const fetchData = async () => {
if (isLoaded) return
try {
let res;
if (method === 'GET') res = await fetch(apiUrl + url, {method, headers});
else res = await fetch(apiUrl + url, {method, headers, body});
setResponse(await res.json());
setIsLoaded(true)
} catch (error) {
setError(error);
}
};
useEffect gets called anytime one of its dependencies changes. You put in [apiUrl, body, headers, method, url]. Both headers and apiUrl are local variables and recreated everytime the hook is called. This means that the references to those variables will change everytime your useFetch executes.
The useEffect sets a state on success or error which causes the re-render which causes the recreation of those variables which causes the useEffect to be called again.
I recommend removing both of those from the dependency array and moving the variables into your useEffect call as they are only used inside there anyways.