Why am I unable to use Okta hooks inside useEffect? (React) - javascript

Recently I posted a question in which I was having some conditional rendering issues with a React/Okta project using TypeScript (for context):
Am I unable to use inline "if" with useEffect in a functional React component?
I've since solved that issue but now I'm experiencing another issue within the following component:
import React, { useState, useEffect } from 'react';
import { useOktaAuth } from '#okta/okta-react';
import { Dropdown } from 'semantic-ui-react';
import { UserInfo } from '../../interfaces/UserInfo';
export const HeaderUserMenu = () => {
// Okta and State hooks
const { authState, oktaAuth } = useOktaAuth();
const [ userInfo, setUserInfo ] = useState<UserInfo>({});
// Log current user data
const logUser = () => {
console.log(userInfo);
}
// Watch the auth state and user info for changes, update state.
useEffect(() => {
if (!authState || !authState.isAuthenticated) {
setUserInfo({});
} else {
oktaAuth.getUser((info: UserInfo) => {
setUserInfo(info);
})
}
}, [authState, oktaAuth]);
// If we have an empty user object, return nothing, otherwise...
if (Object.keys(userInfo).length === 0) {
return null;
} else {
return (
<Dropdown item text={userInfo.name}>
<Dropdown.Item onClick={logUser}>Log User</Dropdown.Item>
</Dropdown>
)
}
}
When I successfully log into Okta, authState and oktaAuth in the useOktaAuth() hook should have stuff in them, and via useState and the useEffect hook, I should be able to fetch the current user's info.
However, it's seeming as if authState and oktaAuth from the hook aren't making it into the effect being run, so even though I'm currently logged into the app, this component is not being rendered at all.
Is it not possible to use hooks like this inside of an effect? The documentation uses this exact pattern so I would have assumed this would be proper usage of useOktaAuth() and the Effect hook, but I can't get it working.
Also, one of the things I had to do to fix my previous issue was the addition of checking for !authState at the beginning of the effect, otherwise it -always- returns an error of:
TypeError: Cannot read property 'isAuthenticated' of null
So I'm not sure if this has something to do with it? There's NO reason that authState or oktaAuth should be null, unless the hook just isn't working? Or there's some issue in combining that hook with useEffect.
Any thoughts?

Related

Why does my authenticated keep changing its value?

I'm creating a login page using react, but when creating the context and an authentication hook I had a problem, the value of "authenticated" does not persist in true, I performed some tests to see its values and I saw that it alternated between true and false, but I didn't identify a loop or anything like that.
import backend from "../utils/backend";
import { useState, useEffect } from "react";
export default function useAuth() {
const [authenticated, setAuthenticated] = useState(false);
useEffect(() => {
console.log(authenticated);
const token = localStorage.getItem("token");
if (token && !authenticated) {
authUser(token);
}
}, []);
setInterval(() => {
console.log(authenticated);
}, 300);
async function authUser(token) {
backend.defaults.headers.Authorization = `Bearer ${JSON.parse(token)}`;
try {
const data = await backend.get("/users/verify").then((res) => {
return res.data;
});
console.log(data, "response from back-end");
setAuthenticated(data);
} catch (error) {
console.log(error);
}
}
return { authenticated };
}
the console result
I tried to recreate the system, I changed the authUser function to setAuthenticated(true), but nothing I did worked
From react official document, you can learn about the custom hook.
https://reactjs.org/docs/hooks-custom.html
Do two components using the same Hook share state? No. Custom Hooks
are a mechanism to reuse stateful logic (such as setting up a
subscription and remembering the current value), but every time you
use a custom Hook, all state and effects inside of it are fully
isolated.
So, whenever you use useAuth in the separated components, it creates isolated state like useState. If you want to persist the value, you will need to use useContext or Redux

React component re-rendering many times

i have a react component thats keep re-rendering idk why but i think the reason is the data fetching
data code :
export function KPI_Stock_Utilisation() {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
setKpi_stock_utilisation((existingData) => {
return response.data;
});
});
}, []);
console.log('data get')
return kpi_stock_utilisation;
}
this log displayed many times , and the log in the component too
component code :
import React from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import { useEffect } from "react";
export default function WarehouseUtilisChart(props) {
let kpi_stock_utilisations =KPI_Stock_Utilisation();
let Stock_utilisation = (kpi_stock_utilisations.length / 402) * 100;
console.log('component render')
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
im new with react i tried useEffect inside the componenets but its not working
Calling the react custom hook KPI_Stock_Utilisation several times will for sure render more than once.
in your case I suggest you use useEffect in the same component as I will show you.
import React,{useEffect,useRef} from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import axios from 'axios';
export default function WarehouseUtilisChart(props) {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
const stock_utilisation= useRef(0);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
stock_utilisation.current = (response.data.length / 402) * 100;
setKpi_stock_utilisation(response.data);
});
//this will guarantee that the api will be called only once
}, []);
//you should see this twice, one with the value 0, and another one, the calculated data
console.log('component render',stock_utilisation.current)
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
To note, if you call this component from more than one location, for sure it will render several times - keep that in mind.
On the other hand, all your variables should always start with a lower case and try to name your variables like this: instead of kpi_stock_utilisation change it to kpiStockUtilisation for a better coding practice
You got into infinite loop.
Its hard to explain why it doesn't work as expected, but I can try.
First of all, useEffect with empty array of dependencies works like componentDidMount and fires only after (!) first render.
So you have some value returned from your let kpi_stock_utilisations =KPI_Stock_Utilisation(); then it rendered, after this your useEffect fires a request and set state, setting of state trigger re-render and new value to return, this new value trigger your parent component to return let kpi_stock_utilisations =KPI_Stock_Utilisation(); might run again.
If you are trying to create a custom hook for fetching some info, follow rules of hooks
I hope it helped you

Invalid hook call error when trying to set state

I have a scenario where I am forced to call a trigger method to show a modal from two different places, one using a hotkey combination and another by clicking on a toolbar button. In order to do so I have the following code, where I call the triggerCustomLinkModal to set the state but then I am hit with the Invalid Hook call error.
import { useState, useCallback, useEffect } from "react"
import { Dialog } from "#blueprintjs/core"
const useLocalState = () => {
const [isShown, setIsShown] = useState(false)
const setState = useCallback((state) => {
setIsShown(state)
})
const getState = useCallback(() => {
return isShown
})
return {
setState,
getState
}
}
export const CustomLinkModalUI = () => {
const { getState } = useLocalState()
return (
<>
<Dialog isOpen={getState()} />
</>
)
}
export const triggerCustomLinkModal = () => {
const { setState } = useLocalState()
setState()
}
Expanding from Chris answer in the comments ( You can't use hooks outside React components. -> so you can't call useLocalState() inside triggerCustomLinkModal since triggerCustomLinkModal is not a React component ):
You don't really need the useCallback hook or even the functions itself. Aaccording to react docs :
Note
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
This also means that using useCallback hook to set a state it doesn't really make sense (because useCallback role is just to return a memoized callback)
What you basically need is a state set up in the closest parrent component and pass the setIsShown as a prop as well as the isShown function.
Your current implementation, even if it weren't for the error, it wouldn't refer to the same state since on each useLocalState() you are initializing a fresh new state (so you are not pointing to the same state in CustomLinkModalUI and triggerCustomLinkModal)

How to re-render a custom hook [duplicate]

This question already has answers here:
How to re-render a custom hook after initial render
(3 answers)
Closed 1 year ago.
I have custom hook named useIsUserSubscribed that checks to see a specific user is subscribed. It returns true if the user is subscribed and false if the user is not subscribed...
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { checkSubscription } from "../services";
// this hook checks if the current user is subscribed to a particular user(publisherId)
function useIsUserSubscribed(publisherId) {
const [userIsSubscribed, setUserIsSubscribed] = useState(null);
const currentUserId = useSelector((state) => state.auth.user?.id);
useEffect(() => {
if (!currentUserId || !publisherId) return;
async function fetchCheckSubscriptionData() {
try {
const res = await checkSubscription(publisherId);
setUserIsSubscribed(true);
} catch (err) {
setUserIsSubscribed(false);
}
}
fetchCheckSubscriptionData();
}, [publisherId, currentUserId]);
return userIsSubscribed;
}
export default useIsUserSubscribed;
...I have a button using this hook that renders text conditionally based on the boolean returned from useIsUserSubscribed...
import React, { useEffect, useState } from "react";
import { add, remove } from "../../services";
import useIsUserSubscribed from "../../hooks/useIsUserSubscribed";
const SubscribeUnsubscribeBtn = ({profilePageUserId}) => {
const userIsSubscribed = useIsUserSubscribed(profilePageUserId);
const onClick = async () => {
if (userIsSubscribed) {
// this is an API Call to the backend
await removeSubscription(profilePageUserId);
} else {
// this is an API Call to the backend
await addSubscription(profilePageUserId);
}
// HOW CAN I RERENDER THE HOOK HERE!!!!?
}
return (
<button type="button" className="sub-edit-unsub-btn bsc-button" onClick={onClick}>
{userIsSubscribed ? 'Subscribed' : 'Unsubscribed'}
</button>
);
}
After onClick I would like to rerender my the useIsUserSubscribed hook So that my button text toggles. Can this be done? Should I use a different approach?
SubscribeUnsubscribeBtn has a dependency on useIsUserSubscribed, but useIsUserSubscribed don't depend on anything from SubscribeUnsubscribeBtn.
Instead, useIsUserSubscribed is keeping a local state. You have a couple of choices here:
Move the state regarding whetehr user is subscribed or not one level up, since you are using Redux, perhaps in Redux.
Communicate to useIsUserSubscribed that you need to change its internal state.
For 1)
const [userIsSubscribed, setUserIsSubscribed] = useState(null);
move this state to Redux store and use it with useSelector.
For 2), return an array of value and callback from the hook, instead of just the value. It will allow you to communicate from component back into the hook.
In useIsUserSubscribed,
return [userIsSubscribed, setUserIsSubscribed];
Then in onClick, you can call setUserIsSubscribed(false), changing the hook's internal state, and re-rendering your component.

React Query with hooks is throwing error, "Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop."

I am working on a React JS project that is using React query, https://react-query.tanstack.com/, React hooks and functional components. But it is throwing error when I use react query for API call.
This is my component and how I use query within it.
const App = () => {
const [ list, updateList ] = useState([])
const info = useQuery(["item-list"], getList, {
retry: false,
refetchOnWindowFocus: false,
})
if (info.status == "success") {
updateList(info.data)
}
return (
//view components here
)
}
This is my getList API call logic
export const getList= async () => {
const { data } = await api.get("8143487a-3f2a-43ba-b9d4-63004c4e43ea");
return data;
}
When I run my code, I get the following error:
react-dom.development.js:14815 Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
What is wrong with my code?
The main reason of that error here is you are running that code block in the if statement in an infinite loop once you have info.status === 'success' as true. Then in every render the updateList is called which triggers an another render.
Probably I would use useEffect hook here in order to listen for changes at info as:
useEffect(() => {
if (info.status == "success") {
updateList(info.data)
}
}, [info])
You should remove that if statement from the body of <App /> component and use the useEffect hook instead as suggested above. By doing this that if statement will be checked once info is changing and not on every render.
Suggested read is Using the Effect Hook.

Categories

Resources