useEffect hook infinite loop - reactjs

I am new to react. here is my problem
import React, { useEffect, useState } from "react";
import { useJwt } from "react-jwt";
import { get_single_user_url } from "../../store/api";
import UrlCard from "../UrlCard/UrlCard";
import "./Urls.css";
function Urls() {
const [urls, setUrls] = useState([]);
const { decodedToken } = useJwt(localStorage.token);
const userId = decodedToken !== null ? decodedToken.userId : "";
useEffect(() => {
const hit = async (userId) => {
get_single_user_url(userId).then((data) => setUrls(data));
const data = await get_single_user_url(userId);
setUrls(data);
console.log(data);
};
hit(userId);
}, []);
return <div className="urls"></div>;
}
export default Urls;
so this useeffect will call a function
get_single_user_data(userId)
and it should return an array of urls from the database. But it returned this
{kind: "ObjectId",
path: "user",
reason: {},
stringValue: """",
value: "",
proto: Object}
This is the function
export const get_single_user_url = async (userId) => {
try {
const response = await axios({
method: "post",
url: "http://192.168.43.62:5000/getUrls",
data: { user: userId },
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
});
console.log(response.data);
return response.data;
} catch (error) {
console.log(error.message);
}
};
here userId is passed through the body. Now in the backend when I print the value of req.body, it gives user property with an empty string.
{ user: "" }
I have tried it without using useEffect but then it goes into an infinite loop.

Since you have an empty dependency array on your useEffect, it will only fire once. It looks like the userId is an empty string when running.
You'll want to add some logic in your hit function to only make the request if userId is not empty. Additionally, to get your effect to run when needed, you should add userId to the dependency array ([userId]).
If userId isn't needed anywhere other than this function, you might use the token instead, and parse the userId in your hit function.
const [urls, setUrls] = useState([]);
const { decodedToken } = useJwt(localStorage.token);
useEffect(() => {
const hit = async (decodedToken) => {
const userId = decodedToken !== null ? decodedToken.userId : "";
if (!userId) {
return;
}
get_single_user_url(userId).then((data) => setUrls(data));
const data = await get_single_user_url(userId);
setUrls(data);
console.log(data);
};
hit(decodedToken);
}, [decodedToken]);

Related

Call useSWR by previous useSWR data.map then got Error(useSWR to get google calendar event)

I write a hook named useGoogleCalendarEvents, Try to get users google calendar event,below is my code.
import { Schedule, User } from '../../types/commons.type';
import { useLocalStorage } from 'usehooks-ts';
import useSWR from 'swr';
import { FetchCalendarList } from '../../types/googleCalendarAPIResponse.type';
import { currentUTCMonthString } from '../../utils/DateUtils';
const baseURL = 'https://www.googleapis.com/calendar/v3';
const fetcher = ([path, method, body = {}, accessToken = '']: [string, string, object, string]) => {
const headers = {
'Content-Type': ['POST', 'PUT', 'PATCH'].includes(method) ? 'application/json' : '',
Authorization: `Bearer ${accessToken}`,
};
const fetchOptions = {
method,
headers,
body: method === 'GET' ? undefined : JSON.stringify(body),
};
const apiURL = `${baseURL}${path}`;
return fetch(apiURL, fetchOptions).then((r) => r.json());
};
export const useGoogleCalendarEvents = () => {
const [user, setUser] = useLocalStorage<User | undefined>('user', undefined);
const events: Array<string> = [];
if (user?.googleAccessToken) {
// get user calendar list
const { data: GoogleCalendarListResult } = useSWR<FetchCalendarList>(
['/users/me/calendarList', 'GET', {}, user?.googleAccessToken],
fetcher,
);
const { startString, endString } = currentUTCMonthString();
const parameters = `timeMin=${startString}&timeMax=${endString}`;
// map loop to get event by calendar id
GoogleCalendarListResult?.items?.map((calendar) => {
const { data: GoogleCalendarEventResult } = useSWR(
[`/calendars/${calendar.id}/events?${parameters}`],
fetcher,
);
});
}
return { events };
};
the code not yet finish but i got the error
"React has detected a change in the order of Hooks called by MySettingPage2."
I modify the code
GoogleCalendarListResult?.items?.map((calendar) => {
const { data: GoogleCalendarEventResult } = useSWR(
[`/calendars/${calendar.id}/events?${parameters}`],
fetcher,
); });
to
['calendarid1', 'calendarid2'].map((calendar) => {
const { data: GoogleCalendarEventResult } = useSWR(
[`/calendars/${calendar.id}/events?${parameters}`],
fetcher,
);
});
It is work, but not flexble. I want to get calendar id list first, then get envets by calendar id
Is anybody known why cause this error? Thank for any reply

Value of props is undefined when accessed inside a function

I have a function which update and add new record to my database. I have passed props called matterId from parent to child component but if I do console log inside the functions, it shows undefined instead.
import React, { useState, useEffect } from 'react';
import { Table, Button, Modal, Form } from 'react-bootstrap';
export default function TimeEntry(props){
const { matterId, timeEntriesData } = props;
console.log(`matterId: ${matterId}`)
const [timeEntries, setTimeEntries] = useState([]);
const addTimeEntry = (e, matterId) => {
console.log(`matterId: ${matterId}`)
e.preventDefault();
fetch(`http://localhost:4000/matters/628607f1c8a4009f2fd4801e/timeEntry`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${ localStorage.getItem('token') }`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
description: entryDescription
})
})
.then(res => res.json())
.then(data => {
if (data === true) {
// fetchData();
alert("New time entry successfully aded.");
closeEdit();
} else {
alert("Something went wrong.");
closeEdit();
}
});
};
};
console.log shows this: matterId: undefined
You are declaring the variable again in the function signature.
Change it something like this
const addTimeEntry = (e) => {
console.log(`matterId: ${matterId}`)
....
}
const { matterId, timeEntriesData } = props;
// This matterId is from props.
console.log(`matterId: ${matterId}`);
const [timeEntries, setTimeEntries] = useState([]);
const addTimeEntry = (e, matterId) => {
// This matterId is from function addTimeEntry
console.log(`functionMatterId: ${matterId}`);
};
// Call function like this
addTimeEntry(e, "id"); // expected console >> functionMatterId: id

Custom react hook throwing error "React has detected a change in the order of Hooks called by ProvidePlaidLink."

I'm trying to make a custom react hook for the plaid api's Link feature. My code for the custom hook looks like this:
function useProvidePlaidLink() {
const auth = useAuth();
// #ts-ignore
if (!auth.user) return null;
const [linkToken, setLinkToken] = useState(null);
const fetchToken = useCallback(async () => {
try {
const config = {
method: "post",
headers: {
'earmark-api-key': process.env.EARMARK_API_KEY,
},
params: {
// #ts-ignore
user_id: auth.user.uid
},
url:'/api/createLinkToken',
}
const response = await axios(config);
setLinkToken(response.data.linkToken);
} catch (error) {
}
}, []);
useEffect(() => {
fetchToken();
}, [fetchToken]);
const onSuccess = useCallback(async (publicToken, metadata) => {
const config = {
method: "post",
headers: {
'earmark-api-key': process.env.EARMARK_API_KEY,
},
params: {
// #ts-ignore
user_id: auth.user.uid,
publicToken: publicToken,
},
url: '/api/exchangeLinkToken',
};
try {
const response = await axios(config);
} catch (error) {
}
}, []);
const config = {
token: linkToken,
onSuccess,
}
const { open, exit, ready } = usePlaidLink(config);
return { open, exit, ready, fetchToken }
}
The first 3 lines are calling my custom auth hook to get the users user id. When the page first renders this is undefined, but a split second later it loads in the uid. However for that split second when it's undefined, my plaid link code throws errors. So I added a quick if statement to the plaid link code to check if it's defined, and if not then return nothing and don't execute the code. This then throws the error React has detected a change in the order of Hooks called by ProvidePlaidLink. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: I don't think it's that big of a deal because if I just wait for the split second (and no one will be clicking on the link bank button instantly on page load) then I am able to run the function no problem. However I don't like seeing that error so how should I go about fixing this?
Thanks
In the beginning, you check if auth.user is null, and if it is, you return out of the function. This will cause that every hook after the null check will not be run, and that will cause the error.
To solve this, you have to run the hooks even if user is null.
This code should solve the problem:
function useProvidePlaidLink() {
const auth = useAuth();
const [linkToken, setLinkToken] = useState(null);
const fetchToken = useCallback(async () => {
try {
// #ts-ignore
if (!auth.user) return;
const config = {
method: "post",
headers: {
'earmark-api-key': process.env.EARMARK_API_KEY,
},
params: {
// #ts-ignore
user_id: auth.user.uid
},
url:'/api/createLinkToken',
}
const response = await axios(config);
setLinkToken(response.data.linkToken);
} catch (error) {
}
}, []);
useEffect(() => {
fetchToken();
}, [fetchToken]);
const onSuccess = useCallback(async (publicToken, metadata) => {
// #ts-ignore
if (!auth.user) return;
const config = {
method: "post",
headers: {
'earmark-api-key': process.env.EARMARK_API_KEY,
},
params: {
// #ts-ignore
user_id: auth.user.uid,
publicToken: publicToken,
},
url: '/api/exchangeLinkToken',
};
try {
const response = await axios(config);
} catch (error) {
}
}, []);
const config = {
token: linkToken,
onSuccess,
}
const { open, exit, ready } = usePlaidLink(config);
return { open, exit, ready, fetchToken }
}

How to create a custom Hooks in reactjs hooks?

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

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