I have this custom http hook with abort when you try to go to a different page (I saw it in a tutorial but I am not truly sure I need it). When I fetch data with it and useEffect(), I have this error on the backend but the request is executed and everything is as planned. My question is, how to improve my code so it does not throw this error and do I need this functionality with abortController() ?
http-hook.ts
import { useCallback, useRef, useEffect } from "react";
import { useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { selectError, showError } from "src/redux/error";
import { selectLoading, startLoading, stopLoading } from "src/redux/loading";
export const useHttpClient = () => {
const dispatch = useDispatch();
const error = useSelector(selectError);
const loading = useSelector(selectLoading);
const activeHttpRequests: any = useRef([]);
const sendRequest = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
dispatch(startLoading());
const httpAbortCtrl = new AbortController();
activeHttpRequests.current.push(httpAbortCtrl);
try {
const response = await fetch(url, {
method,
body,
headers,
signal: httpAbortCtrl.signal,
});
const responseData = await response.json();
activeHttpRequests.current = activeHttpRequests.current.filter(
(reqCtrl) => reqCtrl !== httpAbortCtrl
);
if (!response.ok) {
throw new Error(responseData.message);
}
dispatch(stopLoading());
return responseData;
} catch (err) {
dispatch(showError(err.message));
dispatch(stopLoading());
throw err;
}
},
[]
);
useEffect(() => {
return () => {
activeHttpRequests.current.forEach((abortCtrl: any) => abortCtrl.abort());
};
}, []);
return { loading, error, sendRequest };
};
UserInfo.tsx
import React, { Fragment, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useHttpClient } from "src/hooks/http-hook";
import classes from "./UserInfo.module.css";
const UserInfo = () => {
const { sendRequest } = useHttpClient();
const [currentUser, setCurrentUser] = useState<any>();
const userId = useParams<any>().userId;
useEffect(() => {
const fetchCurrentUser = async () => {
try {
const responseData = await sendRequest(
`http://localhost:5000/api/user/${userId}`
);
setCurrentUser(responseData.user);
console.log("currentUser ", currentUser);
} catch (err) {
console.log(err);
}
};
fetchCurrentUser();
}, [sendRequest ,userId]);
return currentUser ? (
<Fragment>
<div className={classes.cover} />
<div className={classes.user_info}>
<img
alt="user_img"
src={`http://localhost:5000/${currentUser.image}`}
className={classes.user_img}
/>
<div className={classes.text}>
<p>
Name: {currentUser.name} {currentUser.surname}
</p>
<p>Email: {currentUser.email}</p>
<p>Age: {currentUser.age}</p>
</div>
</div>{" "}
</Fragment>
) : (
<p>No current user</p>
);
};
export default UserInfo;
Backend
getCurrentUser.ts controller
const getCurrentUser = async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const userId = req.params.userId;
let user;
try {
user = await User.findById(userId);
} catch (err) {
const error = new HttpError("Could not fetch user", 500);
return next(error);
}
res.json({ user: user.toObject({ getters: true }) });
};
Related
I'm building a searchable index of lessons in Next.js for a school using Airtable as my backend, and I am able to pull the initial page of lessons with getStaticProps and display them. I added a search form that passes an array of search terms through context to a search API component, and though I am able to console log the search terms, I think I am passing the searchTerms array incorrectly, or the search API is not parsing them.
I'm using Auth0 so the component are wrapped with auth, and that all works. Just not returning anything from Airtable when I submit the form.
What I suspect is that I am mangling the fetch or not passing the searchTerms array correctly.
// index.js
import Head from "next/head";
import Image from "next/image";
import Navbar from "../components/Navbar";
import Lesson from "../components/Lesson";
import LessonSearchForm from "../components/LessonSearchForm";
import { table, minifyRecords } from "./api/utils/Airtable";
import { lessonsProvider, LessonsContext } from "../contexts/LessonsContext";
import { useEffect, useState, useContext } from "react";
import { useUser, withApiAuthRequired, getSession } from "#auth0/nextjs-auth0";
export default function Home({ initialLessons }) {
const { lessons, setLessons } = useContext(LessonsContext);
const { searchTerms, setSearchTerms } = useContext(LessonsContext);
const { user, error, isLoading } = useUser();
const [lessonsError, setLessonsError] = useState(null);
useEffect(() => {
setLessons(initialLessons);
}, []);
return (
<div>
<Head>
<title>Lesson Locator</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Navbar user={user} />
<main>
{user && (
<>
<h2 className="text-2xl text-center mb-4">Lesson Locator Search</h2>
<LessonSearchForm />
{lessons &&
lessons.map((lesson) => (
<Lesson key={lesson.id} lesson={lesson} />
))}
</>
)}
</main>
</div>
);
}
export async function getServerSideProps(context) {
try {
const lessons = await table.select({}).firstPage();
return {
props: {
initialLessons: minifyRecords(lessons),
},
};
} catch (err) {
console.error(err);
return {
props: {
err: "Something went wrong!",
},
};
}
}
// LessonSearchForm.js component ( everything before the return statement )
import React, { useState, useContext } from "react";
import { LessonsContext } from "../contexts/LessonsContext";
export default function LessonSearchForm() {
const [lessonTitle, setLessonTitle] = useState("");
const { findLessons } = useContext(LessonsContext);
const [lessonSource, setLessonSource] = useState("");
const [lessonNumber, setLessonNumber] = useState("");
const [textFieldOption, setTextFieldOption] = useState("text");
const [position, setPosition] = useState("");
const [lessonErrorMsg, setLessonErrorMsg] = useState("");
const [isFindingLesson, setIsFindingLesson] = useState(false);
const { searchTerms, setSearchTerms } = useContext(LessonsContext);
const handleChange = (e) => {
setSearchTerms({
...searchTerms,
// Trimming any whitespace
[e.target.name]: e.target.value.trim(),
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log({ searchTerms });
findLessons([searchTerms]);
setLessonErrorMsg("");
setIsFindingLesson(false);
};
// LessonsContext.js
import { createContext, useState } from "react";
const LessonsContext = createContext();
const LessonsProvider = ({ children }) => {
const [lessons, setLessons] = useState([]);
const [searchTerms, setSearchTerms] = useState([]);
const refreshLessons = async () => {
try {
const res = await fetch("/api/getLessons");
const lessons = await res.json();
setLessons(initialLessons);
} catch (error) {
console.log(err);
}
};
const findLessons = async ([searchTerms]) => {
try {
const res = await fetch("/api/searchLessons", {
method: "GET",
// body: JSON.stringify(searchTerms),
// fields: [searchTerms],
headers: { "Content-Type": "application/json" },
});
const foundLessons = await res.json();
console.log(foundLessons);
} catch (error) {
console.error("Error performing search", error);
}
};
return (
<LessonsContext.Provider
value={{
lessons,
setLessons,
refreshLessons,
searchTerms,
setSearchTerms,
findLessons,
}}
>
{children}
</LessonsContext.Provider>
);
};
export { LessonsProvider, LessonsContext };
// searchLessons.js in API (Just trying to parse the Title field for testing)
import { table, minifyRecords, findRecordByFilter } from "./utils/Airtable";
import { useUser, withApiAuthRequired, getSession } from "#auth0/nextjs-auth0";
// export default withApiAuthRequired(async (req, res) => {
const searchLessons = async (req, res) => {
// const { user } = useUser();
// const searchTerms = req.fields;
try {
const records = await table
.select({
filterByFormula: `FIND("${req.title}",{Title})`,
})
.firstPage();
const minifiedRecords = minifyRecords(records);
res.statusCode = 200;
res.json(minifiedRecords);
} catch (err) {
res.statusCode = 500;
res.json({ meg: "something went wrong!" }, err);
}
};
export default searchLessons;
// Airtable.js (Utils for Airtable)
const Airtable = require("airtable");
const base = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY }).base(
process.env.AIRTABLE_BASE_ID
);
const table = base(process.env.AIRTABLE_TABLE_NAME);
const getMinifiedRecord = (record) => {
if (!record.fields.completed) {
record.fields.completed = false;
}
return {
id: record.id,
fields: record.fields,
};
};
const minifyRecords = (records) => {
return records.map((record) => getMinifiedRecord(record));
};
export { table, getMinifiedRecord, minifyRecords };
We have a dynamic route system in our chat app in react frontend and nestjs backend. when we are not refreshing the page but going to a different route, and calling socket event, then the app goes to the previous routes where we visited before. But when we refresh the page and stay in the one route, it stays there.
the problem is, when we change the route, it remembers the previous routes where we visited and goes to that route when socket calling, when we do not refresh the page, this problem occurs.
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { userActiveStatusApi } from '../api/auth';
import { getAllMessageApi, makeReadApi, sendMessageApi } from '../api/chat';
import { selectUserProfile } from '../redux/features/authSlice';
import { selectActiveUser, setUpdateConversation, setUpdateUnreadCount } from '../redux/features/layoutSlice';
import ChattingHomeUi from '../ui/chattingHome/ChattingHomeUi';
import { newSocket } from '../utils/socket';
import { getDateWiseMessages } from '../utils/utils';
const ChattingHome = () => {
let { chatId } = useParams();
const [currentUserProfile, setCurrentUserProfile] = useState({})
const [isLoading, setIsLoading] = useState(true);
const [messagesText, setMessagesText] = useState('')
const [allMessage, setAllMessage] = useState([])
const userProfile = useSelector(selectUserProfile)
const onlineUsers = useSelector(selectActiveUser)
const dispatch = useDispatch();
const userId = userProfile.id;
// check user online status function
function isOnline(id) {
return onlineUsers.indexOf(id) !== -1;
}
// get current user profile function
const getCurrentUserProfile = useCallback(async () => {
async function successHandler(response) {
const res = await response.json();
setCurrentUserProfile(res.user)
setIsLoading(false);
}
async function handleBadReq(response) {
let error = await response.json();
console.log(error.message);
setIsLoading(false);
}
return await userActiveStatusApi(chatId, { successHandler, handleBadReq })
}, [chatId])
// get current user all message list function
const getAllMessage = useCallback(async () => {
async function successHandler(response) {
const res = await response.json();
let sortedData = getDateWiseMessages(res)
setIsLoading(false)
setAllMessage(sortedData)
}
async function handleBadReq(response) {
let error = await response.json();
console.log(error)
setIsLoading(false);
}
return await getAllMessageApi(chatId, { userId: userId }, { successHandler, handleBadReq })
}, [userId, chatId])
// add or update message on conversations List
const updateConversationList = (res) => {
const newMessage = {
users_id: parseInt(res.receiverId),
users_profileImage: currentUserProfile.profileImage,
users_fullname: currentUserProfile.fullname,
message_Status_lastMessage: res?.content,
message_Status_lastMessageTime: res?.createdAt,
message_Status_unreadMessages: 0,
}
dispatch(setUpdateConversation(newMessage))
}
// Send message function
async function handleSubmitMessage() {
if (!messagesText.length) return;
const messageData = {
message: messagesText,
senderId: userId,
type: "text"
}
async function successHandler(response) {
const res = await response.json();
getDeliveryStatus()
updateConversationList(res.result)
setMessagesText('')
getAllMessage();
}
async function handleBadReq(response) {
let error = await response.json();
console.log(error.message);
setIsLoading(false);
}
return await sendMessageApi(chatId, messageData, { successHandler, handleBadReq })
}
const getDeliveryStatus = () => {
newSocket.on('delevered/' + userId, (res) => {
console.log(res)
})
}
// make message as read message
const makeReadMessage = useCallback(async () => {
newSocket.once('message-seen-status' + chatId, (msg) => {
console.log("red", msg);
})
const payload = {
senderId: chatId,
}
async function successHandler(response) {
dispatch(setUpdateUnreadCount(chatId))
}
async function handleBadReq(response) {
let error = await response.json();
console.log(error);
}
return await makeReadApi(userProfile.id, payload, { successHandler, handleBadReq })
}, [dispatch, chatId, userProfile.id])
useEffect(() => {
getCurrentUserProfile()
makeReadMessage()
}, [getCurrentUserProfile, makeReadMessage])
useEffect(() => {
console.log(chatId)
newSocket.on('newMessage/user/' + userId, (msg) => {
if (parseInt(msg.senderId) === parseInt(chatId)) {
makeReadMessage();
}
getAllMessage();
})
}, [chatId, makeReadMessage, getAllMessage, userId])
useEffect(() => {
getAllMessage()
}, [getAllMessage])
return (
<ChattingHomeUi
handleSubmitMessage={handleSubmitMessage}
messagesText={messagesText}
setMessagesText={setMessagesText}
allMessage={allMessage}
userProfile={userProfile}
isOnline={isOnline}
isLoading={isLoading}
currentUserProfile={currentUserProfile} />
);
};
export default ChattingHome;
I'm able to get response from API, but not able to convert response into Json and not able to return the data. It simply return null.
const responseData = async () => {
try{
const response = await axios.get('https://randomuser.me/api')
console.log(response) // console object
const jsonData = await response.json()
return jsonData;
}catch(err){
console.error(err)
}
}
export default function App() {
const [randomUserDataJson,setRandomUserDataJson] = useState('')
useEffect( () => {
responseData().then(randomdata => {
setRandomUserDataJson(randomdata || 'not found')
})
}, []);
return (
<div >
<pre>
<p>{randomUserDataJson}</p>
</pre>
</div>
);
}
Output
not found
You can directly return the axios response nothing but the promise and access the result using then method.
import axios from "axios";
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [randomUserDataJson, setRandomUserDataJson] = useState("");
useEffect(() => {
responseData().then((randomdata) => {
const data = JSON.stringify(randomdata.data);
setRandomUserDataJson(data || "not found");
});
}, []);
return (
<div>
<pre>
<p>{randomUserDataJson}</p>
</pre>
</div>
);
}
const responseData = async () => {
try {
const response = await axios.get("https://randomuser.me/api");
return response;
} catch (err) {
console.error(err);
}
};
codesandbox - https://codesandbox.io/s/withered-bush-iicqm?file=/src/App.js
You don't have to do const jsonData = await response.json(), axios will deserialize the response to JS Object for you. Just remove that line and it would work. Also, you can't render JS object as a child of a React Component, so it has to be stringified.
import axios from 'axios';
import { useState, useEffect } from 'react';
const responseData = async () => {
try{
const response = await axios.get('https://randomuser.me/api')
console.log(response) // console object
return response;
}catch(err){
console.error(err)
}
}
export default function App() {
const [randomUserDataJson,setRandomUserDataJson] = useState('')
useEffect( () => {
responseData().then(randomdata => {
setRandomUserDataJson(randomdata || 'not found')
})
}, []);
return (
<div >
<pre>
<p>{JSON.stringify(randomUserDataJson, null, 2)}</p>
</pre>
</div>
);
}
I am trying to pull data from an Axios Get. The backend is working with another page which is a React component.
In a function however, it doesn't work. The length of the array is not three as it is supposed to be and the contents are empty.
I made sure to await for the axios call to finish but I am not sure what is happening.
import React, { useState, useEffect } from "react";
import { Container } from "#material-ui/core";
import ParticlesBg from "particles-bg";
import "../utils/collagestyles.css";
import { ReactPhotoCollage } from "react-photo-collage";
import NavMenu from "./Menu";
import { useRecoilValue } from "recoil";
import { activeDogAtom } from "./atoms";
import axios from "axios";
var setting = {
width: "300px",
height: ["250px", "170px"],
layout: [1, 3],
photos: [],
showNumOfRemainingPhotos: true,
};
const Collages = () => {
var doggies = [];
//const [dogs, setData] = useState({ dogs: [] });
const dog = useRecoilValue(activeDogAtom);
const getPets = async () => {
try {
const response = await axios.get("/getpets");
doggies = response.data;
//setData(response.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
};
useEffect(() => {
const fetchData = async () => {
getPets();
};
fetchData();
}, []);
return (
<>
<NavMenu />
<ParticlesBg type="circle" margin="20px" bg={true} />
<br></br>
<div>
{doggies.length === 0 ? (
<div>Loading...</div>
) : (
doggies.map((e, i) => {
return <div key={i}>{e.name}</div>;
})
)}
</div>
<Container align="center">
<p> The length of dogs is {doggies.length} </p>
<h1>Knight's Kennel</h1>
<h2> The value of dog is {dog}</h2>
<h2>
Breeders of high quality AKC Miniature Schnauzers in Rhode Island
</h2>
<section>
<ReactPhotoCollage {...setting} />
</section>
</Container>
</>
);
};
export default Collages;
Try doing the following:
const [dogs, setData] = useState([]);
[...]
const getPets = async () => {
try {
const response = await axios.get("/getpets");
doggies = response.data;
setData(response.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
};
const fetchData = async () => {
getPets();
};
useEffect(() => {
fetchData();
}, []);
No idea if it will actually work, but give it a try if you haven't.
If you don't use useState hook to change the array, it won't update on render, so you will only see an empty array on debug.
As far as I can tell you do not return anything from the getPets() function.
Make use of the useState Function to save your doggies entries:
let [doggies, setDoggies ] = useState([]);
const getPets = async () => {
try {
const response = await axios.get("/getpets");
return response.data;
} catch (err) {
// Handle Error Here
console.error(err);
}
return []
};
useEffect(() => {
setDoggies(await getPets());
});
I used setState inside the getPets function. Now it works.
const Collages = () => {
const [dogs, setData] = useState([]);
const dog = useRecoilValue(activeDogAtom);
const getPets = async () => {
try {
const response = await axios.get("/getpets");
setData(response.data);
} catch (err) {
// Handle Error Here
console.error(err);
}
};
useEffect(() => {
const fetchData = async () => {
getPets();
};
fetchData();
}, []);
i am a newbie to react but i'm learning and need your help here.
I use Auth0 for Authentication and i have implemented their react sample in parts:
https://auth0.com/docs/quickstart/spa/react/01-login
This are parts of my code:
App.js:
<Auth0Provider
domain={AUTH_CONFIG.domain}
client_id={AUTH_CONFIG.clientId}
redirect_uri={AUTH_CONFIG.callbackUrl}
onRedirectCallback={onRedirectCallback}
>
<Router history={history}>
<RequireAuthentication>
<MyTheme>
<MyLayout />
</MyTheme>
</RequireAuthentication>
</Router>
</Auth0Provider>
Auth0Provider:
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "#auth0/auth0-spa-js";
import jwtDecode from "jwt-decode";
import axios from "axios";
import AUTH_CONFIG from "./auth0Config";
import { useDispatch } from "react-redux";
import * as authActions from "app/auth/store/actions";
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
const initAuth0 = async () => {
console.log("initAuth0 start");
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
const isAuthenticated = await auth0FromHook.isAuthenticated();
console.log("Authenticated from init: " + isAuthenticated);
setIsAuthenticated(isAuthenticated);
setLoading(false);
console.log("initAuth0 end");
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (params = {}) => {
setPopupOpen(true);
try {
await auth0Client.loginWithPopup(params);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await getUserData();
setUser(user);
dispatch(authActions.setUserDataAuth0(user));
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
if (!auth0Client) {
console.warn("Auth0 Service didn't initialize, check your configuration");
return;
}
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = await getUserData();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
dispatch(authActions.setUserDataAuth0(user));
};
const getAccessToken = async () => {
const accessToken = await auth0Client.getTokenSilently({
audience: AUTH_CONFIG.identity_audience,
scope: "read:allUsers read:UserPermission"
});
return accessToken;
};
const getIdToken = async () => {
if (!auth0Client) {
console.warn("Auth0 Service didn't initialize, check your configuration");
return;
}
const claims = await auth0Client.getIdTokenClaims();
return claims.__raw;
};
const getTokenData = async () => {
const token = await getIdToken();
const decoded = jwtDecode(token);
if (!decoded) {
return null;
}
return decoded;
};
const getUserData = async () => {
console.log("getuserdata");
const tokenData = await getTokenData();
const accessToken = await getAccessToken();
return new Promise((resolve, reject) => {
const { sub: userId } = tokenData;
const UserService =
"https://localhost:44312/api/v1/usermanagement/user/" + userId;
axios
.get(UserService, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Methods": "GET,HEAD,OPTIONS,POST,PUT",
"Access-Control-Allow-Headers":
"Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers",
"Content-Type": "application/json",
Authorization: "Bearer " + accessToken
}
})
.then(response => {
resolve(response.data);
})
.catch(error => {
// handle error
console.warn("Cannot retrieve user data", error);
reject(error);
});
});
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p)
}}
>
{children}
</Auth0Context.Provider>
);
};
RequireAuthentication:
import React, { useEffect } from "react";
import { useAuth0 } from "app/auth/AuthProvider";
import { SplashScreen } from "#my";
import history from "#history";
export const RequireAuthentication = ({ children }) => {
const { isAuthenticated, loading } = useAuth0();
useEffect(() => {
console.log("checkAuth");
if (!loading) checkAuth();
// eslint-disable-next-line
}, []);
const checkAuth = () => {
console.log("checkAuth isAuthenticated: " + isAuthenticated);
console.log("checkAuth loading: " + loading);
if (!isAuthenticated && !loading) {
history.push("/login");
}
};
return isAuthenticated ? (
<React.Fragment>{children}</React.Fragment>
) : (
<SplashScreen />
);
};
callback.js:
import React, { useEffect } from "react";
import { SplashScreen } from "#my";
import { useAuth0 } from "app/auth/AuthProvider";
function Callback(props) {
const { isAuthenticated, handleRedirectCallback, loading } = useAuth0();
useEffect(() => {
const fn = async () => {
if (!loading) {
console.log("handleRedirectCallback: " + loading);
await handleRedirectCallback();
}
};
fn();
}, [isAuthenticated, loading, handleRedirectCallback]);
return <SplashScreen />;
}
export default Callback;
The problem is that the RequireAuthentication Component is rendered before the Auth0Provider is completely initialized and therefore i get never the isAuthenticated on "true".
The RequireAuthentication Component is a child of the Auth0Provider. Is it possible to wait for the Auth0Provider is fully initialized before rendering the RequireAuthentication Component???
What is the right way here?? Am I completely wrong?
Thanks
Chris
Depend on loading and isAuthenticated items in useEffect so that component will re render once they change.
useEffect(() => {
console.log("checkAuth");
if (!loading) checkAuth();
// eslint-disable-next-line
}, [loading, isAuthenticated]);