Vue-router callback when navigation is cancelled - javascript

My Vue app shows a loading screen whenever a route is loading because code-split routes sometimes take a few seconds to load. It does this using a global beforeEach hook.
// App component
this.$router.beforeEach(() => { this.loading = true })
this.$router.afterEach(() => { this.loading = false })
this.$router.onError(() => { this.loading = false })
The problem with this is if I create a guard on a route definition (like beforeEnter), calling next(false) won't trigger afterEach or onError, so it leaves the app in a loading state forever.
One thing I tried was to use beforeResolve instead of beforeEach. This didn't work out because my beforeEnter route guards are asynchronous, and I want to show the loading screen while they execute.
Another possible solution is to always call next(error) instead of next(false) so that the onError hook is triggered. But that's a bug waiting to happen later on, when someone tries to use the navigation guard API as intended.
One last option is to isolate the this.loading logic into a globally-accessible class or event bus and make route guards handle it on a case-by-case basis.
But my preference would be to set it and forget it. Is there a global hook that gets called when navigation is cancelled? And why isn't afterEach called in this scenario?

Related

How can I make a request in React when a specific page is navigated away from or closed using React Router v6?

I have a React application using React Router v6 where I would like to call a GraphQL mutation (using Apollo Client) when a user leaves a specific page in the app either by clicking the browser's back button, navigating away from the page using in-app buttons (page unmounts), or closing the tab/browser.
Perhaps this isn't entirely possible in the way I want but also if it isn't possible with Apollo Client but is possible with a more typical request, that's fine.
Here are some of the things I have tried
Calling it from the clean up function in the useEffect hook
I expected this to work whenever the page unmounts but I was naive in understanding how React responds to events like closing a browser or tab as React does not become aware of this. It did work for browser back arrow use and react router navigation though, albeit not super reliably.
visibilitychange event handler
const onChangeHandler = (e) => {
e.preventDefault();
callMutation();
};
useEffect(() => {
window.addEventListener('visibilitychange', onChangeHandler);
return () => {
window.removeEventListener('visibilitychange', onChangeHandler);
};
}, []);
This event listener failed to trigger when navigating away from the page at all. It only triggered on actions like minimizing the browser and switching tabs which I don't want at all and when the page was refreshed.
beforeunload event and useBeforeUnload hook
Next, I tried the above with the beforeunload event but this event would only trigger on page refresh.
I then used the useBeforeUnload hook that React Router 6 added and had the same issue where only a page refresh would trigger the event.
useBeforeUnload(
useCallback(() => {
callMutation();
}, []),
);
So in summary, wondering if there is any way for me to call out to my GraphQL API when a specific page is navigated away from in the cases of using the browser's back arrow, when the page is unmounted, and when the tab is closed. Thanks for any help/info!
Unloading the page is different than unmounting a component because navigation occurred. You may very well need to apply different logic to handle the different ways a user goes "away from a page". When the page is reloaded or the tab/window is closed, any running Javascript is immediately quit. In other words, any unmounting useEffect hook cleanup functions won't run because nothing is running anymore; it's all terminated.
Example using the window.beforeunload event to handle a page reload, tab closing etc and also handling the component simply unmounting.
useEffect(() => {
const unloadCallback = (event) => {
event.preventDefault();
event.returnValue = ""; // * required for browser compatibility
callMutation(); // call external function/effect
return ""; // * required for browser compatibility
};
// listen for page unloading
window.addEventListener("beforeunload", unloadCallback);
// return cleanup function to cancel listener and call function
// is simply unmounting
return () => {
window.removeEventListener("beforeunload", unloadCallback);
callMutation(); // call external function/effect
};
}, []);

Authentication with Firebase and context API in react give me a warning. Is this the right approach?

I am trying to set up an authentication on my web application in React using firebase and Context API.
I am using Context API since as long as I understood I cannot save my jwt token in local storage in order to not be vulnerable to XSS attack and at the moment I do not want to use Redux.
in my App.js I have:
const {setUserInfo} = useContext(userInfoContext);
useEffect(() => {
auth.onAuthStateChanged(user => {
if (user) {
setUserInfo({jwtToken: user.za});
} else {
setUserInfo({jwtToken: null});
}
console.log(user);
});
}, [setUserInfo]);
The methos "auth.onAuthStateChanged" is triggered every time I logged in or I logged out using firebase.auth.
The compiler tell me that to eliminate the warning I should have "[setUserInfo]" instead of "[]". However, doing as he say, the method setUserInfo is executed twice. There is a better way to achieve the result without a warning?
Your problem is that you don't clean up your effect when it is recomputed. As soon as you add setUserInfo to the dependency array, the effect is executed whenever its value changes. This means that you could potentially register many auth.onAuthStateChanged if the value of setUserInfo changes.
auth.onAuthStateChanged returns an unsubscribe function. You can simply return this function inside your effect, which will make react execute the unsubscribe function whenever the hook is executed again and prevent you from having multiple active listeners. I suggest you read more about this topic here.

React Redux - quick flash of previous state before dispatch issue

I'm building a React + Redux app with a Node.js backend, and one of the features is that a user can view profiles of other users. To do this, I have a section in the Redux state called users that looks like:
{
...,
users: {
user: {},
isLoading: true
}
}
Every time the /users/:id route is rendered, a getUser(id) action is dispatched and fills the state with the received information.
The main issue is when a user views user1's profile page (therefore redux state is now filled with user1's profile) and then views user2's profile page, the dispatch getUser(2) action is called right after the page is rendered. Since user1's info is still in the state, it will flash their info for a very short time, and then show the loading spinner until the new user is loaded.
I read about dispatching a resetUser(id) action on every unmount of the page, but I'm not sure if this is the right way to go. Also, one of the features is if a user is viewing their own page, they have an edit button which redirects them to /edit-profile. If I reset the state on every unmount, I'll have to fetch their profile again (in the edit page), even though I just did that when they viewed their page.. And that doesn't seem like it makes sense.
Any ideas how to solve this? Thanks!
The render phase runs after mounting. And you stated that previous data is being shown before new data. It seems that you have asynchronous mounting:
async componentDidMount() {}
It will continue rendering even before mounting phase is completed. To avoid issue, you may use synchronous nature of mounting:
componentDidMount(){}
Where you'll call api data.
Now, when you reach to rendering phase it will have new data available before rendering and won't flash you old data.
You now may be wondering how to call api asynchronously. You can create a asynchronous function and call that function inside the synchronous componentDidMount.
componentDidMount() {
yourAsyncFunc()
}
async yourAsyncFunc() {} // api call
How can I do this with React hooks?
While using useEffect, don't implement async:
useEffect(async () =>
Implement it simply:
useEffect(() => {
// you can still use asynchronous function here
async function callApi() {}
callApi()
}, []) // [] to run in similar to componentDidMount
If you miss second parameter to useEffect then you are not ensuring it to run on mounting phase. It will run before, after, and in the rendering phase depending on case.
Implementing something like resetUser(id) seems to be the right way here. I use this approach in my current project and this does solve the problem. The other approach of removing async keyword from useEffect callback as mentioned in another answer didn't work for me (I use hooks, redux-toolkit, Typescript).
After dispatching this action, your state should look something like
{
...,
users: {
user: null,
isLoading: false,
}
}
If you are using hooks, you can dispatch the action this way:
useEffect(() => {
const ac = new AbortController();
...
return () => {
dispatch(resetUser(null));
ac.abort();
};
}, []);
Action could look something like this:
resetListingDetails(state, action) {
// Immer-like mutable update
state.users = {
...state.users,
user: null,
};
}

Electron.js + React/Redux - best practices

So, here the thing.
We have main process and renderer and they`re is isolated from each other and only bridge is IPC messages(to main process) or BrowserWindow webcontents send(to main process).
I'm handling my events in componentDidMount like so:
ipcRenderer.on('deviceWasPlugged', this.deviceWasPluggedListener);
And ofc I'll remove this listener with componentWillUnount for preventing memory leaking in case the component was destroyed:
ipcRenderer.removeListener('deviceWasPlugged', this.deviceWasPluggedListener);
Obviously I have a such method in my Class/Component
deviceWasPluggedListener = (e, somedata) => {
// boring stuff goes there...
}
All is peachy, except one fact - It doesn't seems right as for me.
I still think, there should be a better place to keep my events listeners.
I think, I cant keep it in redux middleware, coz event based model won't allow me to unsubscribe of this events in redux middleware and multiple events will be created when I'll be trying to dispatch smth, even if I'll switch it by action.type:
const someMiddleware = store => next => action => {
if (action.type === 'SOME_TYPE') {
// ...
}
next(action);
};
export default someMiddleware;
So, guys, please tell me is there any better place to keep my backend events? It should be triggered every time I want it, and it shouldn't cause memory leaking problem with maxEventListeners.

Ember - observer in previous route fires when property set in didTransition hook of next route

In my index route, I have an observer that fires an action when the user object is set in the session service, as shown below. This works fine- the console logs 'index route observer fired' and the action fires.
routes/index
session: Ember.inject.service(),
sendTheAction: function() {
console.log('index route observer fired');
this.send('checkLicense');
}.observes('session.user'),
actions: {
checkLicense: function() {
if (this.get('session.user.email)) {
//get the user's email and send off an AJAX request.
}
},
}
I also have a logout route which (among other things) sets session.user to an empty object, and then transitions to the login route. I simply use a link-to helper pointing to 'logout' to initiate this.
routes/logout
session: Ember.inject.service(),
actions: {
didTransition: function() {
this.set('session.user', {});
this.transitionTo('login');
}
}
If I am in the index route and I click logout, the transition to logout begins. Then, when session.user is set to {} in the logout route's didTransition hook, the observer in the index route fires. The console logs 'index route observer fired' but then I get a console error saying
Error while processing route: logout Nothing handled the action 'checkLicense'.
I'm not sure how the observer in the previous route can still be fired by a change in the logout route's didTransition hook, as I thought didTransition only fired when the transition was fully resolved.
I think that it is then looking for the action "checkLicense" in the logout route.
Can anyone clarify why that observer still fires, and how to get around this?
The observer still fires and this is the expected behavior. The reason is; routes are SINGLETONs; they are not destroyed upon transitions. So; if you define an observer within a route and watch for values that might be updated by other routes, components, controllers, etc. then your observer will still work!!! So; be careful about using observers within singletons (controllers, services, routest, etc.).
I created a twiddle that shows the exact case you mentioned. The valid question here in fact is "why is the send action within index.js is delegated to the other route?" Here is the answer from the Ember.js api. It says "Sends an action to the router, which will delegate it to the currently active route..." in send method definition. So; the error occurs; because current active route is not index anymore; action is not found!
So; what can you do? Why are throwing an action to yourself within index.js? Just make a normal function call!!! If you still need the action; you can also call the same function from within the action. See the modified twiddle in order to see what I mean.
To sum up; observers within singletons will work no matter what if the dependent key(s) get(s) updated! Hope; it is all clear now. Best Regards.

Categories

Resources