react-query, Invalid hook call - reactjs

I am trying to make a function to easily execute queries. Perhaps I initially did everything wrong, I will be glad if you correct me.
I created file Request.js:
import React from "react";
import { useQuery } from 'react-query'
export const Request = (method = "GET", body = {}, url = 'http://localhost:3000/notes') => {
const parameters = {
headers: {
'Content-Type': 'application/json',
},
method: method,
};
if (method == "POST" || method == "PUT") {
parameters.body = JSON.stringify(body);
}
const { status, data, isFetching, error } = useQuery("repoData", async (url, parameters) => {
return await fetch(
url, parameters
)
});
if (status === 'loading') {
return <div>loading...</div> // loading state
}
if (status === 'error') {
return <div>{error.message}</div> // error state
}
return data;
};
And in index.js i called this function
import { Request } from './Request/Request';
console.log(Request());
But i have error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

Related

How to logout automatically when session expires while using createAsyncThunk and axios (withcredential) option using react and redux toolkit?

I am trying to logout the user when the session expires after a certain period of time. I am using redux-toolkit with react for my API calls and, hence, using the createAsyncThunk middleware for doing so.
I have around 60 API calls made in maybe 20 slices throughout my application. Also, there is a async function for logout too that is fired up on the button click. Now the problem that I am facing is that if the session expires, I am not able to logout the user automatically. If I had to give him the message, then I had to take up that message from every api call and make sure that every screen of mine has a logic to notify the Unautherised message.
I did check a method called Polling that calls an API after a certain given time. And I believe that this is not a very efficient way to handle this problem.
**Here is a little code that will help you understand how my API calls are being made in the slices of my application. **
// Here is the custom created api that has axios and withcredentials value
import axios from "axios";
const api = axios.create({
baseURL:
process.env.NODE_ENV === "development" ? process.env.REACT_APP_BASEURL : "",
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
export default api;
// My Logout Function!!
export const logoutUser = createAsyncThunk(
"userSlice/logoutUser",
async (thunkAPI) => {
try {
const response = await api.get("/api/admin/logout");
if (response.status === 200) {
return response.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
return thunkAPI.rejectWithValue(e.response.data);
}
}
);
I want to dispatch this function whenever there is a response status-code is 401 - Unauthorised. But I don't want to keep redundant code for all my other API calls calling this function. If there is a middleware that might help handle this, that would be great, or any solution will be fine.
// Rest of the APIs are called in this way.
..........
export const getStatus = createAsyncThunk(
"orgStat/getStatus",
async (thunkAPI) => {
try {
const response = await api.get("/api/admin/orgstat");
if (response.status === 200) {
return response.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
return thunkAPI.rejectWithValue(e.response.data);
}
}
);
const OrgStatusSlice = createSlice({
name: "orgStat",
initialState,
reducers: {
.......
},
extraReducers: {
[getStatus.pending]: (state) => {
state.isFetching = true;
},
[getStatus.rejected]: (state, { payload }) => {
state.isFetching = false;
state.isError = true;
state.isMessage = payload.message;
},
[getStatus.fulfilled]: (state, { payload }) => {
state.isFetching = false;
state.data = payload.data;
},
},
});
.......
If needed any more clearence please comment I will edit the post with the same.
Thank You!!
import axios from 'axios'
import errorParser from '../services/errorParser'
import toast from 'react-hot-toast'
import {BaseQueryFn} from '#reduxjs/toolkit/query'
import {baseQueryType} from './apiService/types/types'
import store from './store'
import {handleAuth} from './common/commonSlice'
import storageService from '#services/storageService'
// let controller = new AbortController()
export const axiosBaseQuery =
(
{baseUrl}: {baseUrl: string} = {baseUrl: ''}
): BaseQueryFn<baseQueryType, unknown, unknown> =>
async ({url, method, data, csrf, params}) => {
const API = axios.create({
baseURL: baseUrl,
})
API.interceptors.response.use(
(res) => {
if (
res.data?.responseCode === 1023 ||
res.data?.responseCode === 6023
) {
if(res.data?.responseCode === 1023){
console.log('session expired')
store.dispatch(handleSession(false))
return
}
console.log('Lopgged in somewhere else')
store.dispatch(handleSession(false))
storageService.clearStorage()
// store.dispatch(baseSliceWithTags.util.resetApiState())
return
// }, 1000)
}
return res
},
(error) => {
const expectedError =
error.response?.status >= 400 &&
error.response?.status < 500
if (!expectedError) {
if (error?.message !== 'canceled') {
toast.error('An unexpected error occurrred.')
}
}
if (error.response?.status === 401) {
// Storage.clearJWTToken();
// window.location.assign('/')
}
return Promise.reject(error)
}
)
try {
let headers = {}
if (csrf) headers = {...csrf}
const result = await API({
url: url,
method,
data,
headers,
params: params ? params : '',
baseURL: baseUrl,
// signal: controller.signal,
})
return {data: result.data}
} catch (axiosError) {
const err: any = axiosError
return {
error: {
status: errorParser.parseError(err.response?.status),
data: err.response?.data,
},
}
}
}
I am also using RTK with Axios. You can refer to the attached image.

pass function as parameter to custom hooks

I am trying to pass a function as a parameter to custom hooks I created. The file that uses the hooks and it is it self a custom hook is this:
export const MainStore = () => {
...
const checkForRefreshTokenUpdate = async () => {
...
}
const { getAssetData } = assets(checkForRefreshTokenUpdate());
...
}
second file:
export const assets = ( checkForRefreshTokenUpdate ) => {
const { customAxios } = interceptedAxios(checkForRefreshTokenUpdate);
/**
* Fetches asset data.
* #returns
*/
const getAssetData = async (params) => {
const url = buildUrl({
baseUrl: getBaseUrl(),
endpoint: "/api/asset",
params
});
const reqOpts = {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${getAccessToken()}`
}
};
return customAxios.get(url, reqOpts).then(res => { res.data });
};
return { getAssetData }
}
third file:
export const interceptedAxios = ( checkForRefreshTokenUpdate ) => {
const customAxios = axios.create();
customAxios.interceptors.request.use(async (config) => {
if (config.url.includes(getBaseUrl()) && config.headers["Authorization"] != null) {
await checkForRefreshTokenUpdate()
.then((token) => {
if (token != null) {
config.headers["Authorization"] = `Bearer ${token}`;
}
});
}
return config;
},
(error) => {
return Promise.reject(error);
});
return { customAxios }
}
I keep getting error: ReferenceError: checkForRefreshTokenUpdate is not defined.
Is there anything wrong in syntax?
try to pass the checkForRefreshTokenUpdate function without parenthesis:
wrong :
const { getAssetData } = assets(checkForRefreshTokenUpdate());
correct :
const { getAssetData } = assets(checkForRefreshTokenUpdate);
I agree with previous comment that first mistake you made is because you immediately called function while passing to custom "hook". So, first you need to update that part of the code as previous comment suggest to you, to pass just an reference and not the result(promise).
Second mistake I spotted is this:
await checkForRefreshTokenUpdate()
.then((token) => {
if (token != null) {
config.headers["Authorization"] = `Bearer ${token}`;
}
});
You must not use await with .then, await itself will pause the excecution until promise is resolved, so you need to update code to this:
const token = await checkForRefreshTokenUpdate();
if (token != null) {
config.headers["Authorization"] = `Bearer ${token}`;
}
You must either use .then syntax or async/await, you cant mix them.
Anyway beside my first answer and second one from Milos i would like to define checkForRefreshTokenUpdate inside third file (interceptedAxios), that's cleaner and less confusion because it's not used anywhere else, even if you wanna use it you can easily export it from there!

Next.js - React Custom Hook throws Invalid hook call

Hi I am quite new to react and this is for a learning project.
In react under next.js want to check for the existence of a certain folder on the server. To achieve that I implemented an api twigExists.js in pages/api and a custom hook twigExistsRequest.js in the library folder:
import {useEffect, useRef} from "react";
import {webApiUrl} from "#/library/webHelpers";
export function useTwigExistsRequest({
parentDirSegment,
name,
action,
treeStateDispatch
}) {
const nameExists = useRef('not');
useEffect(() => {
if ('' !== name) {
async function fetchNameValidation() {
try {
const response = await fetch(
webApiUrl() + '/branchName',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({parentDirSegment, name})
}
);
const answer = await response.json();
if (undefined !== answer['exists']) {
nameExists.current = answer['exists'];
}
else if (undefined !== answer['err']) {
console.log(answer['err']);
}
} catch (err) {
console.log(err);
}
}
fetchNameValidation().then(() => {
nameExists.current === 'exists'
&& treeStateDispatch({
action,
name,
dirSegment: parentDirSegment
});
})
}
});
}
The following error is thrown at the useRef line, line 10:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component.
I am using an almost identical approach to get the structure of a special folder with its subfolders and it is working fine. Working example:
import {useEffect, useRef} from "react";
import {webApiUrl} from "#/library/webHelpers";
export default function useChangeBranchRequest({
data,
setData
}) {
let postData;
const storeEffect = useRef(0);
if ('skip' !== data) {
storeEffect.current += 1;
postData = JSON.stringify(data);
}
useEffect(() => {
if (0 !== storeEffect.current) {
async function fetchData() {
try {
const response = await fetch(
webApiUrl() + '/changeBranch',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: postData
});
const json = await response.json();
setData(JSON.parse(json['tree']));
} catch (error) {
console.log(error);
}
}
fetchData()
.then(() => {
return (<></>);
});
}
}, [storeEffect.current]);
}
I can't see: What is wrong in the first example??
Edit due to question: useTwigExistsRequest is called from this file:
import {useTwigExistsRequest} from "#/library/twigExistsRequest";
export default function twigExistsHandler({
parentDirSegment,
name,
action,
treeStateDispatch
}) {
useTwigExistsRequest({
parentDirSegment,
action,
name,
treeStateDispatch
});
}
trying to avoid a direct call from:
import {ACTIONS} from "#/library/common";
import {useState} from "react";
import twigExistsHandler from "#/library/twigExistsHandler";
export default function PlgButton({dirSegment, action, text, treeStateDispatch}) {
const [info, SetInfo] = useState('');
const [parentDirSegment, SetParentDirSegment] = useState('');
// name validation, triggered by SetInfo. Returns strings 'false' or 'true':
// reset Button after execution
if (info) SetInfo('');
return (
<button
className="btn btn-secondary btn-sm new-plg-btn"
onClick={() => {
clickHandler(action);
}}
>
{text}
</button>
);
function clickHandler(action) {
let name;
switch (action) {
case ACTIONS.add:
name = window.prompt('New name:');
twigExistsHandler({
parentDirSegment: dirSegment,
name,
action,
treeStateDispatch
});
break;
case ACTIONS.dup:
name = window.prompt('Dup name:');
twigExistsHandler({
parentDirSegment: dirSegment.slice(0,dirSegment.lastIndexOf('/')),
name,
action,
treeStateDispatch
});
break;
case ACTIONS.del:
window.confirm('Irrevocably delete the whole playground?')
&& treeStateDispatch({
info: '',
dirSegment,
action
});
break;
}
}
}

React / useEffect doesn't call/dispatch action creator

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

Prevent Multiple Token Refreshes With API React Hook

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 !

Resources