Reputation: 522
UPDATE: I created a minimum reproducible sample here: https://react-wsaei2.stackblitz.io Editor link: https://stackblitz.com/edit/react-wsaei2
In my app I'm using a sendBeacon function in beforeunload to unlock a database document when someone closes the page without doing it themselves. Here's how I'm doing that in a useEffect with an empty array so it only runs once on start:
// Add event listener to run code before window closes
window.addEventListener("beforeunload", unlockStory);
return () => {
window.removeEventListener("beforeunload", unlockStory);
}
And here's the unlockStory function:
const unlockStory = (e) => {
e.preventDefault();
if (props.storyID && !success && !loggedOut) {
console.log("Unlocking story. success, loggedOut", success, loggedOut);
debugger;
navigator.sendBeacon(`/api/stories/${props.storyID}/${loggedOut}/${success}`, JSON.stringify({body: {locked: true}}));
}
e.returnValue = "What's going on?";
}
As you see I don't want the beacon to send every time - only if the loggedOut == false (i.e. I don't want to send the Beacon to unlock if the user is already logged out).
The problem is for some reason in the unlockStory function loggedOut is always false, even if it was true right beforehand! I put a manual refresh button on the page to check like so:
const handleRefresh = () => {
console.log("Handling refresh: success, loggedOut", success, loggedOut);
window.location.reload(false);
}
The output is:
Handling refresh: success, loggedOut false true
Unlocking story. success, loggedOut false false
WHYYY????
Another odd thing is that the debugger;
line in the unlockStory function doesn't ever get triggered on normal refreshes or page closes, it only gets triggered on refreshes that are causes by me making file changes so npm automatically refreshes the open pages.
Please help, I'm at a loss, thank you!
Upvotes: 0
Views: 1808
Reputation: 8428
You need to define success
and loggedOut
(and other used variables) as effect dependencies.
useEffect(() => {
const unlockStory = (e) => {
e.preventDefault();
console.log("Inside unlockStory. success, loggedOut:", success, loggedOut);
if (!success && !loggedOut) {
console.log("Inside if")
navigator.sendBeacon(`/api/stories/`, JSON.stringify({body: {locked: true}}));
}
e.returnValue = "What's going on?";
}
// Add event listener to run code before window closes
window.addEventListener("beforeunload", unlockStory);
return () => {
window.removeEventListener("beforeunload", unlockStory);
}
}, [success, loggedOut]); // Run on every success or loggedOut change
There is no need to manually remove event listeners as:
React also cleans up effects from the previous render before running the effects next time.
Sometimes is easier to use class based components:
class StartClass extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedOut: false,
success: false,
storyObj: {
segCount: 2
}
}
}
componentDidMount() {
this.setState( { loggedOut: true } );
window.addEventListener("beforeunload", this.unlockStory);
return () => {
window.removeEventListener("beforeunload", this.unlockStory);
}
}
handleRefresh = () => {
let {success, loggedOut} = this.state
console.log("Handling refresh: success, loggedOut", success, loggedOut);
window.location.reload(true);
}
unlockStory = (e) => {
e.preventDefault();
let {success, loggedOut} = this.state
console.log("Inside unlockStory. success, loggedOut:", success, loggedOut);
if (!success && !loggedOut) {
console.log("Inside if")
navigator.sendBeacon(`/api/stories/`, JSON.stringify({body: {locked: true}}));
}
e.returnValue = "What's going on?";
}
render() {
return (
<Container fluid>
<Row>
<Col>
<p>
To reproduce:<br/> Open console, then click the Refresh page button below. When the alert pops up check the console and you'll see that the loggedOut variable has changed from true to false.
</p>
<p>
The same behavior occurs when you refresh via the browser, but you can't see the log as the console is cleared upon refresh.
</p>
{this.state.loggedOut && <Button onClick={this.handleRefresh}>Refresh page</Button>}
</Col>
</Row>
</Container>
);
}
}
Upvotes: 3