From few days I am facing a challenge. I want to call a method when user close a tab or window, so before closing the tab I want to execute a method. I am working in functional component ReactJS. It will be very helpful if get some solution regarding my query. I am placing my logout method inside that but it is getting triggered multiple times ?
window.addEventListener("beforeunload", (ev) =>
{
ev.preventDefault();
onLogOut();
return ev.returnValue = 'Are you sure you want to close?' ;
});
I need to implement the onLogOut
But the way I am using it the logout method is triggered multiple times. Any solution to make it call only once ?
If you're using React, please consider implementing such code in useEffect with a clean up function, for more information about clean up function in react
useEffect(() => {
window.addEventListener("beforeunload", (ev) => { ev.preventDefault(); __onLogOut(); return ev.returnValue = 'Are you sure you want to close?'; });
return() => {
window.removeEventListener('beforeunload')
}
},[])
Related
Hoping to get some guidance on how to best work with async data calls in React.
I have a page that has a FullCalendar component which will show the events given to it in a state, i.e., simplifying a lot,
return (
...
<FullCalendar events={ events } />
)
What I need to do to get the events is first to make a call to Google Calendar's APIs to fetch the Oauthed user's calendars, then looping through each calendar, make a call to Google calendar to fetch that calendar's events. Something like (very simplified):
let eventsToShow = [];
apiCalendar.listCalendars({}).then(({result}) => {
// At this point I have the list of the user's calendars
result.items.forEach((calendar) => {
// Now I have all the events on a given calendar
apiCalendar.listEvents(calendar.id).then(({result}) => {
eventsToShow.push({id: result.id, start: result.start, end: result.end});
})
})
})
setEvents(eventsToShow);
Right now, I have a button on the page, and I'd like to kick off the calls to fetch the calendars and events when I click the button (because I have a separate button that does the Oauth sequence). Later, my intent is to store something from the Oauth so that the page will just pull the calendars and events automatically if the token is present.
In the above pseudocode, I have various pieces working - the FullCalendar component can show hard-coded events, my various API calls do fetch the correct date - but I'm having trouble figuring out how to put it all together.
I suspect I have to use a useEffect as well as use await, but the nested nature of the API calls is confusing me. If I do have to use useEffect, I'm also not sure how to get the calls to trigger off a button click for now.
I've tried having an onClick function for the button that just leads to essentially the second block of pseudocode above. This worked for causing the API calls to run (as evidenced by console.logs along the way), but the setEvents part at the end doesn't work, I suspect because of the async nature of things.
Help? Am I completely misguided?
If what you want is to bind the api calls to the click of the button you wouldn't need a useEffect.
Definitely you can improve the readability of the async calls with the use of async await. From what you shared I get that the async call should be something like this:
const fetchCalendarEvents = async()=>{
try{
const calendars = await apiCalendar.listCalendars()
const events = await Promise.allSettled(calendars.map(c=>{
return apiCalendar.listEvents(c.id)
}))
return events
}
catch (error){
console.error(error)
}
}
You can add the required parameters that you need to do the api call.
Within the component then you can define the onClick event on the button which will trigger this function something like
const handleEventsFetch = async()=>{
const events = await fetchCalendarEventts(
//...params
)
setEvents(events)
}
If you ever want the fetching to be on component load you can definitely do that in a useEffect. Note you cant use async-await in useEffect but we can use then/catch or an IIFE
//then
useEffect(()=>{
fetchCalendarEvents(
//...
).then(
setEvents
)
},[])
//iife
useEffect(()=>{
(async()=>{
const events = await fetchCalendarEvents(
//...
)
setEvents(events)
})()
},[])
Note I don't know what the params for the api calls are so beware that you might need to add dependencies to the useEffect. In consequence of that you might also need to add some if checks before running the api call so that you don't run it multiple times if any of the dependencies change
EDIT: Add transformation on returning the events
const fetchCalendarEvents = async () => {
try {
const calendars = await apiCalendar.listCalendars();
//rename this so that it is more descriptive
const calendarsWithEvents = await Promise.allSettled(
//make the map function async!
calendars.map(async (c) => {
// now you can use await
const calEvents = await apiCalendar(c.id);
return {
calendarId: c.id,
events: calEvents,
};
})
);
return calendarsWithEvents;
} catch (error) {
console.error(error);
}
};
I want a function, to send a request to a server (don't care about response) before a user refreshes the page.
I've tried using the componentWillUnmount approach but the page refresh doesn't call this function. e.g.
import React, { useEffect } from 'react';
const ComponentExample => () => {
useEffect(() => {
return () => {
// componentWillUnmount in functional component.
// Anything in here is fired on component unmount.
// Call my server to do some work
}
}, []) }
Does anyone have any ideas how to do this?
You could try listening for the window.beforeunload event.
The beforeunload event is fired when the window, the document and its
resources are about to be unloaded. The document is still visible and
the event is still cancelable at this point.
useEffect(() => {
const unloadCallback = (event) => { ... };
window.addEventListener("beforeunload", unloadCallback);
return () => window.removeEventListener("beforeunload", unloadCallback);
}, []);
Note: This will respond to anything that causes the page to unload though.
Note 2:
However note that not all browsers support this method, and some
instead require the event handler to implement one of two legacy
methods:
assigning a string to the event's returnValue property
returning a string from the event handler.
You can do this, almost, by checking if the page has been refreshed.
Source: https://developer.mozilla.org/en-US/docs/Web/API/Navigation_timing_API
Example:
if (window.performance) {
console.info("window.performance is supported");
console.info(performance.navigation.type);
if (performance.navigation.type == performance.navigation.TYPE_RELOAD) {
console.info( "This page is reloaded" );
} else {
console.info( "This page is not reloaded");
}
}
All examples I am seeing online are using React Components. I am a newbie to react. So any explanation will be helpful, and what I should do to achieve this.
export default function Review() {
...
useEffect(() => {
console.log("useeffect called")
window.addEventListener("beforeunload", save());
}, []);
...
return (...);
Here is a CodeSandbox.io link. Here you will find that I have 2 pages 1 home the other dashboard.
When I go to dashboard. I get the Alert. But Leaving Dashboard I do not get the Alert Message. beforeunload is not working how I expect it to.
https://codesandbox.io/s/react-router-basic-forked-8uvvi?file=/
An alternate way to call a function when a page/component is about to be unmounted is to add it as a return from your useEffect(). For example:
useEffect(() => {
return(() => save())
}, []);
CodeSandbox Example
You are not passing an onbeforeunload callback, you are immediately invoking the alert.
useEffect(() => {
console.log("useeffect called");
// subscribe event
window.addEventListener("beforeunload", alert("HI"));
}, []);
Create/define a callback that does one, or more, of the following:
Prevents the default on the event object
Assigns a string to the event object's returnValue property
Returns a string from the event handler.
window.beforeunload
Note: To combat unwanted pop-ups, some browsers don't display prompts created in beforeunload event handlers unless the page has
been interacted with. Moreover, some don't display them at all.
Code suggestion
useEffect(() => {
const handler = (e) => {
// Cancel the event
e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
// Chrome requires returnValue to be set
e.returnValue = "";
// ... business logic to save any data, etc... before the window unloads
save();
return "";
};
// subscribe event
window.addEventListener("beforeunload", handler);
return () => {
window.removeEventListener("beforeunload", handler);
};
}, []);
I want to create a function that refreshes the page and then call element and click:
const handlePDF = () => {
window.location.reload(false);
pdfRef.current.click()
};
I had thought about setTimeout but it doesn't work like that either.
I imagine that I have to save that order in memory once the page is refreshed but I don't know how to do it.
I appreciate your help guys.
You can use sessionStorage on window load event to solve that.
1. Listen window on load
window.onload = () => {
let reloading = sessionStorage.getItem("reloading");
if (reloading) {
sessionStorage.removeItem("reloading");
pdfRef.current.click();
}
}
2. Save a session on handlePDF call
const handlePDF = () => {
sessionStorage.setItem("reloading", "true");
window.location.reload(false);
};
I had a similar requirement to your's, before.
The solution that I settled with is to use sessionStorage to flag states, so that some flag is available to you, upon page refresh.
Then, you'd look for that flag in a useEffect callback.
But first, let's create a constant, so that we don't aimlessly repeat ourselves.
const handlePdfFlag = 'handle_pdf';
Then, create the flag in localStorage, then reload the page.
const handlePDF = () => {
sessionStorage.setItem(handlePdfFlag, 'true');
window.location.reload(false);
};
Then, in some useEffect, you'd pick up the flag, ensuring that you delete the flag afterwards.
useEffect(() => {
if (sessionStorage.getItem(handlePdfFlag) === 'true' && pdfRef.current) {
sessionStorage.removeItem(handlePdfFlag);
pdfRef.current.click();
}
});
If you want a function that does some code based on something changing then maybe the useEffect hook will work for you.
I keep struggling with the same issue with React Hooks. The dependency array.
I have many hooks that should trigger and handle different events.
For instance:
useEffect(() => {
const doSomethingWith = (notification: Notifications.Notification) => {
...
setUser({ notifications: badgeCount });
};
notificationListener.current = Notifications.addNotificationReceivedListener(
(notification) => {
if (user) {
doSomethingWith(notification);
}
}
);
return () => {
Notifications.removeNotificationSubscription(notificationListener);
};
}, [setExpoPushToken, setUser, user]);
In this simplified code, you can see that I track notifications and when I get it, I use the data in the notification and update the user object with it.
However, this whole thing will run whenever the user is updated. Like, if I update the user first name - the notification hook will run. That makes little sense to me. It forces me to add if statements inside these hook functions which is a waste and makes my code ugly.
What am I missing? How do I handle this better?