HMR strange behavior in Safari with store pattern - Vue2 - javascript

I've implemented a simple user store in Vue 2 to keep track of the currently authenticated user.
It looks like this:
const user = {
isGuest: true,
state: {
name: ''
},
save (user) {
this.isGuest = false
this.state = user
},
clear () {
this.isGuest = true
this.state = { }
}
}
I also have a simple authentication service (implemented as a plugin) which fetches the user from the api and saves him into the store. Whenever the user logs in, OR the app is loaded the auth service fetches the user and saves him to the store.
I am trying to access the store from another component (my navbar). I have this in my component to import the userStore:
import user from '../store/user'
export default {
data () {
return {
user
}
}
}
This is all working really nicely in Chrome and in Firefox.
However, in Safari I get some weird behavior:
If I force the navbar's data to do a hot reload by saving the Navbar.vue file, the data is fetched from the store. However, in all other cases (page reload, or after a vue-router redirect after log in) the navbar's data is not updated with the current user store.
How can I fix this to work in all browsers?
EDIT:
I've got a little more information:
If I sit on the refresh button in Safari, randomly I sometimes get the correctly rendered data from the user store, and randomly I don't.
My hunch is that sometimes the api call returns quick enough for the data to be available when the navbar data is rendered, and sometimes it does not.
In other browsers, even when the data is not available at load, it the reactive data is updated when the data becomes available. This is not the case in Safari for some reason (except that the hot reload does work if I force it to update by re-saving the file)
EDIT #2:
I can confirm that this is the issue. My data does not update when the store is updated.
I proved this by having the navbar component sleep for 2 seconds before fetching data from the store, and sure enough the data is populated correctly every single time now in Safari.
So the question now becomes more fundamental, how can I use the store pattern in Safari?

I found the root of the problem.
I was actually using a proxy object around the store. Apparently this messed things up in Safari only. Without it everything works fine.

Related

How to share data between components in angular

I use observable to send a value from one component to another (data is sent to the subscriber after clicking on this other component, i.e. via subject) I subscribe in another component and everything works fine until I refresh the page, after refreshing the page the component is recreated and after recreation the subscriber has no data as he did not go through the first component.How can I solve the problem?
I tried using rxjs operators as shareReplay but it didn't work like shareReplay
As your Angular app is destroyed and rebuilt when the page is refreshed, unfortunately you will lose all user state that is not saved somewhere. This is a common problem in building UIs so there are a number of tools available to combat this
Strategy:
Store your user state when an important change is made. This is called persisting state
Fetch and reapply your saved state on reload. This is called hydrating state
Options:
Persist to local storage and check for local storage values on reload to hydrate with
Persist within the users URL (simple values only), e.g. modifying the URL in some way which can be checked on reload. Assuming you are dealing with a single page, query parameters or fragments may be the way to go
Persist to a database via a POST/PATCH call and perform a GET request on reload to check for values to hydrate with
None of these methods are inbuilt into an RxJS operator (as far as I know) but we can easily leverage RxJS to achieve any of the above strategies with little effort. The tap operator is often used specifically to handle side effects, i.e. operations which should happen as a coincidence of an RxJS emission. That is precisely what we want here, in simple terms:
"If the subject emits a value, also trigger an operation which
persists the user state"
"On page load, check for any available saved user state and emit via the
relevant subject, hydrating the observables which the components will consume"
See example implementation below
tab.service.ts
type TabType = 'first' | 'second'
#Injectable({
providedIn: 'root'
})
export class TabService {
tabSelectedSubject: BehaviorSubject<TabType> = new BehaviorSubject<TabType>('first')
tabSelected$: Observable<TabType> =
this.tabSelectedSubject
.pipe(
tap(tab: TabType) => {
// ... your persist code here
this.saveTab()
},
distinctUntilChanged()
)
constructor() {
// ... your hydrate code here
this.fetchAndApplyTab();
}
saveTab(): void {
localStorage.setItem('tab', tab)
}
fetchAndApplyTab(): void {
const savedTab: TabType | null = localStorage.getItem('tab');
if (savedTab) {
this.tabSelectedSubject.next(savedTab)
}
}
}
In this case, we are exploiting the fact that our service is:
A singleton, so only loaded once per app (i.e. provided in the app root)
The service will be instantiated in the first component that loads which also injects it
This allows us to put our fetchAndApplyTab() logic in tab.service.ts's constructor and keep the code self-contained. However, depending on your use case, you may instead want to run fetchAndApplyTab() from your component manually itself.
This is happening because everything is in memory, and on page refresh all is lost, due the fact that angular app is re-initializing. You need to persist the state, for example write it into local storage, for this you could use "tap" operator from rxjs. And also in loading you could read data from localstorage end emit-it, for this you could use app_initializer hook.
there are 2 days majority to pass data between components
If both components are interconnected it means the parent or child
relationships then you can pass data with input-output decorators.
you can use the common service to share data between 2 components.
In SPA application if you refresh the browser then all in memory objects and observables are not present you need to again go back to the screen where it will be initialize.

Persisted Vuex and storage timing issue in NuxtJS

So I have a timing issue I believe with Vuex and https://github.com/championswimmer/vuex-persist package.
What I am doing:
I wrote a plugin to authenticate with auth code grant with pixie.
This plugin adds my own and custom vuex auth module.
I am storing the entire Vuex store along with tokens and things in encrypted local storage.
I am passing the store to my service.
When I try to access my store immediately, a specific field in the state, the data is not there but it is there if I just dump the store object.
This is my code where the problem occurs.
import authModule from './store/modules/auth/auth.js'
import { AuthService } from "~/plugins/auth/services/AuthService";
export default({app, store, $axios}, inject) => {
store.registerModule('auth', authModule, { preserveState: !!store.hasModule('auth') })
inject('auth', new AuthService(store))
// this will be incorrect and token will be null ===> this.$store.state.auth.token !== null
console.log('app.$auth.hasAccessToken', app.$auth.hasAccessToken)
// this will be OK and token will be there when I expand the object in the console
console.log(app.store)
}
So what I think happens is it takes time to load data from encrypted storage and when I want to access it immediately it is not there.
If I just dump out the object, it gets updated I guess before it's printed to the console and I see what I need to see.
I was wondering if anyone knows how I could extend default vuex mutations to add events when mutation takes place or when the store is updated. I could then register event listener so I would only proceed when everything is loaded if that makes sense.
So essentially what I want is a way to tell that my store is fully loaded on go on only when everything is ready.
Does that makes sense? Maybe there is a better way?

Prevent navigation to certain pages/routes

I have a Next.js (fix may not necessarily have anything to do with Next.js) app that I want to limit some navigation. This page in the Next.js docs mentions "you should guard against navigation to routes you do not want programmatically", but I'm unsure how to do this. Let's say I have the following pages:
/bikes
/boats
/cars
and I only want to allow a user to see /bikes. How would I be able to do this. I'm able to redirect from an undesired page to an acceptable page, but only after the undesired page loads. Is there a way to redirect before this (maybe by changing the URL as soon as it is changed)?
I appreciate that this is an old question, however, I wish I had known this answer to it earlier than I did, so I would like to answer it for the record.
Our next.js app had a relatively complex set of permissions associated with accessing each page (with the ability to access the page, or different data presented on it) based on the authentication status of a user as well as the authorization scopes granted to them.
The solution that we used was the to return a redirect from getServerSideProps(). Normally one would return something like the following:
const getServerSideProps = (context) => {
return {
props: {
myProp: 1
}
};
}
However, it is also possible to return a redirect. This redirect is performed on the server side prior to the page content being sent to the browser:
const getServerSideProps = (context) => {
return {
redirect: '/login'
};
}
This is relatively new functionality - it was only pulled in towards the end of last year, and there isn't much documentation for it anywhere, but it perfectly serves this use case.

History API's state does not persist in deployed application

I have an issue in understanding the History API's state. The problem I am facing is the different behaviour between localhost and the actual deployed application.
So after I save something in state, and refresh the page I see two different behaviours.
localhost keeps a copy post refresh
production environment doesn't keep a copy after the refresh.
I have gone through the documentation,
https://developer.mozilla.org/en-US/docs/Web/API/History_API
it talks about, the state should be serializable and the max size. But I am storing exactly the same data, in both the environments, because it's the same flow of the application.
Could anyone help with this?
Edit
I am using react-router-dom, here is the code snippet:
// inside the component,
this.props.history.push('/some/route', {
data: data
});
// in component did mount
if (this.props.location.state) {
// do something with this.props.location.state
})

Reload component data based on vuex state

I am creating one single admin panel for several web shops. To keep track of what website I am currently editing I have a website object in my vuex state. The website can be changed by a button that dispatches an action on the state.
The admin dashboard page is a page showing statistics for the selected website, the data for these statistics is currently saved on the page component itself.
Now I have a problem when I change to another website when I am on the dashboard. Because the url is the same for the dashboards, the statistics don't change. I suppose this is because vue-router has not detected a change to the url so there is no need to reload the component, however I would like this to happen when the website is changed. Or at least a way to know that the website has changed so I could manually call the ajax source again.
I have just started using vue a few weeks ago and did not find a way to accomplish this.
You can subscribe to Vuex mutations and actions: https://vuex.vuejs.org/api/#subscribe
On the created hook of your admin dashboard component, assuming you're injecting the Vuex store, you could have something like:
created: function() {
this.unsubscribe = this.$store.subscribe((action, state) => {
if (action.type === 'changeWebsite') { // Whatever your action is called
this.updateWebsiteData(action.payload); // Method to update component data
}
});
},
beforeDestroy: function() {
this.unsubscribe();
},

Categories

Resources