Function inside useEffect fire twice even with empty dependency array - reactjs

I have this example from https://github.com/vercel/next.js/blob/canary/examples/with-firebase-authentication/utils/auth/useUser.js
The effect works fine (fires once) but for some reason, the functions inside are called twice.
useEffect(() => {
const cancelAuthListener = firebase
.auth()
.onIdTokenChanged(async (user) => {
console.log('once or twice?')
if (user) {
// This fires twice
const userData = await mapUserData(user)
setUserCookie(userData)
setUser(userData)
} else {
removeUserCookie()
setUser()
}
})
const userFromCookie = getUserFromCookie()
if (!userFromCookie) {
router.push('/')
return
}
setUser(userFromCookie)
console.log(' i fire once')
return () => {
console.log('clean up')
cancelAuthListener()
}
}, [])
How can I make it to fire once?
I added some console logs:
On the first render I get: 'i fire once', 'once or twice', 'once or twice'
If I leave the page the cleanup console log fires (as it's supposed to do)
Many thanks
Later edit:
this is the code
export const mapUserData = async (user) => {
const { uid, email } = user
const token = await user.getIdToken()
return {
id: uid,
email,
token
}
}
If getIdToken() gets 'true' as an argument it will force a refresh regardless of token expiration.
https://firebase.google.com/docs/reference/js/firebase.User#getidtoken

Solved!!
the user was calling getIdToken(true) which forces a refresh.
https://firebase.google.com/docs/reference/js/firebase.User#getidtoken
Sorry guys, my bad!!!

You have a setState() inside useEffect thats the culprit, where useEffect having empty params [], one request on initial mount and another when do
setUser(userData) the component re-renders and useEffect() is invoked again.
Instead of using user as state, try using as ref and check. That might resolve this.

Related

react-router-dom not updating useParmams() when Link to same path with different url

It renders fine if I click the link in the <MeetNew> component from a different component, but when a <MeetNew> Link is clicked from the <User> component, the page doesn't load correctly.
on the component
const User = () => {
let { id } = useParams()
let res2;
const [userInfo, setUserInfo] = useState({
user: {},
listItems: []
})
const { listItems } = userInfo
useEffect(() => async () => {
try {
if (id) {
const res2 = await axios.get(`/api/listItems/${id}`)
setUserInfo({ listItems: res2.data })
console.log('render')
}
} catch (err) {
console.error(err)
}
}, [id])
return (
...
)
I feel like I'm not using useParams() correctly or useEffect correctly. When I click the link the URL change is correct, but useParams() doesn't re-render or re-mount my component.
I guess you are referring to pages as components? Is it possible that your id param does not change, so your useEffect is not activated, thus there is no loading of items?
The useEffect looks very suspect to me. It doesn't run any logic as part of the effect other than to return an async cleanup function. This probably isn't what you meant to implement.
Refactor the useEffect callback to declare an async function and only invoke it if there is a truthy id dependency value.
useEffect(() => {
const getItems = async () => {
try {
const res2 = await axios.get(`/api/listItems/${id}`);
setUserInfo({ listItems: res2.data });
} catch (err) {
console.error(err);
}
}
if (id) {
getItems();
}
}, [id]);
What I suspect is happening is that you are using react#18 and rendering the app into a React.StrictMode component. In react#18 the StrictMode component intentionally "double-mounts" react components as a way to ensure Reusable State. The returned cleanup function runs and makes the GET request and the state is updated. The only occurs on the initial mounting/render of the component, subsequent renders occur normally. This part is just a hunch though by looking at just your code without testing a running demo.

Supabase onAuthStateChange() triggers when switching tabs in React

I have the following code in my React project using Supabase:
// supabaseClient.ts
export const onAuthStateChangedListener = (callback) => {
supabase.auth.onAuthStateChange(callback);
};
// inside user context
useEffect(() => {
const unsubscribe = onAuthStateChangedListener((event, session) => {
console.log(event);
});
return unsubscribe;
}, []);
However, every time I switch tabs away from the tab rendering the website to something else, and back, I see a new log from this listener, even if literally no change happened on the website.
Does anyone know the reason for this? The useEffect inside my user context component is the only place in my app where the listener is being called. To test, I wrote this dummy function inside my supabaseClient.ts file:
const testFunction = async () => {
supabase.auth.onAuthStateChange(() => {
console.log("auth state has changed");
});
};
testFunction()
This function also renders every time I switch tabs. This makes it a little annoying because my components that are related to userContext re render every time a tab is switched, so if a user is trying to update their profile data or something, they cannot switch tabs away in the middle of editing their data.
Supabase onAuthStateChange by default triggers every time a tab is switched. To prevent this, when initializing the client, add {multiTab: false} as a parameter.
Example:
const supabase = createClient(supabaseUrl, supabaseAnonKey, {multiTab: false,});
Here is my solution to the same problem. The way I've found is saving the access token value in a cookie every time the session changes, and retrieve it when onAuthStateChange get triggered, so I can decide to not update anything if the session access token is the same.
// >> Subscribe to auth state changes
useEffect(() => {
let subscription: Subscription
async function run() {
subscription = Supabase.auth.onAuthStateChange(async (event, newSession) => {
// get current token from manually saved cookie every time session changes
const currentAccessToken = await getCurrentAccessToken()
if (currentAccessToken != newSession?.access_token) {
console.log('<<< SUPABASE SESSION CHANGED >>>')
authStateChanged(event, newSession)
} else {
console.log('<<< SUPABASE SESSION NOT CHANGED >>>')
}
}).data.subscription
// ** Get the user's session on load
await me()
}
run()
return function cleanup() {
// will be called when the component unmounts
if (subscription) subscription.unsubscribe()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

ReactJS delay update in useState from axios response

I am new to react js and I am having a hard time figuring out how to prevent delay updating of use state from axios response
Here's my code:
First, I declared countUsername as useState
const [countUsername, setUsername] = useState(0);
Second, I created arrow function checking if the username is still available
const checkUser = () => {
RestaurantDataService.checkUsername(user.username)
.then(response => {
setUsername(response.data.length);
})
.catch(e => {
console.log(e);
})
}
So, every time I check the value of countUsername, it has delay like if I trigger the button and run checkUser(), the latest response.data.length won't save.
Scenario if I console.log() countUseranme
I entered username1(not available), the value of countUsername is still 0 because it has default value of 0 then when I trigger the function once again, then that will just be the time that the value will be replaced.
const saveUser = () => {
checkUser();
console.log(countUsername);
}
Is there anything that I have forgot to consider? Thank you
usually there is a delay for every api call, so for that you can consider an state like below:
const [loading,toggleLoading] = useState(false)
beside that you can change arrow function to be async like below:
const checking = async ()=>{
toggleLoading(true);
const res = await RestaurantDataService.checkUsername(user.username);
setUsername(response.data.length);
toggleLoading(false);
}
in the above function you can toggle loading state for spceifing checking state and disable button during that or shwoing spinner in it:
<button onClick={checking } disabled={loading}>Go
i hope this help
.then is not synchronous, it's more of a callback and will get called later when the api finishes. So your console log actually goes first most of the time before the state actually saves. That's not really something you control.
You can do an async / await and return the data if you need to use it right away before the state changes. And I believe the way state works is that it happens after the execution:
"State Updates May Be Asynchronous" so you can't really control when to use it because you can't make it wait.
In my experience you use the data right away from the service and update the state or create a useEffect, i.g., useEffect(() => {}, [user]), to update the page with state.
const checkUser = async () => {
try {
return await RestaurantDataService.checkUsername(user.username);
} catch(e) {
console.log(e);
}
}
const saveUser = async () => {
const user = await checkUser();
// do whatever you want with user
console.log(user);
}

Update state in setInterval via dispatch outside component

I currently have a functional component Form that triggers a task to occur. Once the submission is complete, I create a setInterval poll to poll for the status of the task. The code roughly looks like
export function Form(props: FormProps) {
const dispatch = useDispatch()
const pollTaskStatus = () => {
const intervalId = setInterval(async() => {
const response = await fetchTaskStatus() // Function in different file
if (response.status === 'COMPLETE') {
dispatch(Actions.displayTaskComplete())
clearInterval(intervalId)
}
})
}
const submitForm = async() => {
await onSubmitForm() // Function in different file
pollTaskStatus()
}
return (
...
<button onClick={submitForm}>Submit</button>
)
}
When the action is dispatched, the redux store is supposed to be updated and a component is supposed to update alongside it showing a message that the task is complete. However, I see the action logged with an updated store state but nothing occurs. If I just try to dispatch the same action with useEffect() wrapped around it outside the submitForm functions, the message appears. I've searched online and people say that you need to wrap useEffect around setInterval but I can't do that because the function that calls setInterval is not a custom hook or component. Is there a way to do this?
It's a bit difficult to answer your question without seeing all the code.
But my guts feeling is that this might no have anything to do with React.
const pollTaskStatus = () => {
const intervalId = setInterval(async() => {
console.log('fire to fetch')
const response = await fetchTaskStatus() // Function in different file
if (response.status === 'COMPLETE') {
console.log('success from fetch')
dispatch(Actions.displayTaskComplete())
}
})
}
Let's add two console lines to your code. What we want to see is to answer the following questions.
is the setInterval called in every 500ms?
is any of the call finished as success?
how many dispatch has been fired after submission
If you can answer all these questions, most likely you can figure out what went wrong.

ReactJS: Maximum update depth exceeded when fetching data from local-storage token

I am getting the above error on the following code. Will anyone help me find out what could be wrong with this code? Thanks
const [userData, setUserData] = useState({});
useEffect(() => {
const user = auth.getCurrentUser();
setUserData(user);
});
This is the getCurrentUser function
export function getCurrentUser() {
try {
const jwt = localStorage.getItem(tokenKey);
return jwtDecode(jwt);
} catch (ex) {
return null;
}
}
You should be very cautious while using useEffect.
Below is what you have used in your implementation. Which is also the problem why the error.
useEffect(() => {
const user = auth.getCurrentUser();
setUserData(user);
});
To understand what exactly happening just a background of useEffect. When useEffect is being used without any dependencies it'll execute every time the component gets rendered. Please have a look here for more info.
So in this case in useEffect you are trying to update the state of the component which will cause the component to re-render, as discussed again useEffect will get called again state gets updated and the loop goes on.
setUserData() -> component re-render -> setUserData() -> component re-render -> setUserData() -> component re-render ....the cycle goes on
So ideally you should be getting the current user details only once after the component mounted and this can be done by passing [] as dependency to useEffect as below
useEffect(() => {
const user = auth.getCurrentUser();
setUserData(user);
}, []);
Hope this helps.
You have to try this
useEffect(() => {
function getData() {
const user = auth.getCurrentUser();
setUserData(user);
}
getData()
},[]);

Resources