Reload component data based on vuex state - javascript

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();
},

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.

Run a component function every time its rendered

I've got an Ionic Angular app that calls an API every time the view changes and stores the value in ionic storage.
Every page has a 'header' component that is supposed to display this value from storage.
Inside ngOnInit(){} I fetch the value from storage and the value updates. However, it only does it once per page view. So if I view the homepage, for example, go to page 2, and back to the homepage, the code won't re-execute.
I imagine this is because the page doesn't actually unmount when you change the view, and therefore has no reason to 're-construct'.
constructor(){} only runs on the first construct for that view
ngOnInit() {} only runs the first init for that view
ionViewDidLoad() {} doesn't run because its not a view and that's an ionic page life cycle hook
I'm basically looking for a way to call an API, store the value in ionic storage, and on every page change, update the value in the header.
I've been thinking this might be achievable with Observables, but I'm not sure how this would work with ionic storage.
Or even get the value from storage in the view using one of the ionic view lifecycle hooks and pass it into the header as a prop, but that just seems messy.
The reason I'm using storage is that the balance gets used elsewhere and not just in the header and it saves me making the call multiple times if it's just available in storage.
Any recommendations appreciated
export class HeaderComponent {
balance = 0.00;
constructor(
private storage: Storage,
) { }
async ngOnInit() {
this.storage.get('balance').then(data => {
this.balance = data;
});
}
}
If you're positive that ngOninit in HeaderComponent is only firing once, then that means that it's not being destroyed between route changes. That would make sense if your component is present in all of your views.
I would not use local storage to distribute your application state to your app components. If your app state is relatively simple, then observables are the way to go. Create a service that holds the state of your application in an observable. Every time you fetch data from the API, push the new data to the observable. Then in your HeaderComponent subscribe to the observable in the ngOnInit hook so that your component data can be automatically synced to the application state any time there is a change.

Should I use redux dispatch during initial rendering?

I'm trying to build a E-Commerce Store and it requires that I initially load a list of trending products on the home page.
Here, I can simply do without redux and simply display the data (roughly) of this sort
const trendingProducts = await get('/api/trendingProducts')
render(){
<TrendingProducts data={this.trendingProducts.data} />
}
I am using redux in my application. So should I do a dispatch elsewhere to fetch the trending products ?
All in all, Should I always handle every single fetch / render using only Redux dispatch ?
Redux is a store management for your application's state. Redux's dispatch is used to dispatching actions that aims to update your application's state in some way.
Hence if your application logic requires displaying information that belongs to your application's state - you need to take it from Redux store. If such information is not yet available into Redux store - you need to obtain it from some source (e.g. fetch) and use dispatch to update your application's state. If information, you're trying to display is not part of your application's state - you can display it directly, but in this case you'll need to handle React lifecycle events by yourself too since React re-draws components upon component's state change.
UPDATE: Your example code will work fine if you'll put your trendingProducts into component's state:
class MyComponent {
constructor() {
super();
this.state = {
trendingProducts: {}
}
}
componentWillMount() {
fetch('/api/trendingProducts').then(data => this.setState({trendingProducts: data}));
}
render() {
return (
<TrendingProducts data={this.state.trendingProducts}/>
)
}
}
That is very subjective and there is no correct answer but I can tell you by my experience.
Always keep your business logic separate from your component
If you're making API calls then you should definitely dispatch an action rather writing this in your component because there are lot of redux-like stores are emerging and it might happen that later you want to change your store using some different architecture. Your view layer should be independent on your API calls (business logic). In this way, while refactoring your app again, you'll just have to change the logic and your view will remain the same.
I'm writing this on my experience where we started refactoring the whole app from backbone to React. We had models and collections, we changed the whole html but we didn't change any business logic initially, just the views were deprecated but later we removed the business logic too using redux (iteratively). You should write your code in such a way that it has maximum reusability after all that's what React is and that's how you should write your front-end code.
Also, the component's state can reside in component where the whole app doesn't get affected. e.g. showing or hiding of a third-pane or overlay etc.

Access React app from other React app on the same page

I have a situation where I have written the front-end of my website as micro-services. Every functional section of my website is a react-habitat application bundled with css and html. I have done this so I can re-use these sections wherever I want on the site.
My problem is however in the checkout area I need to pass the customerId from the login application (on successful response) into the delivery address application so I can bring back the default address. I don't want to create a new application and duplicate the code for login and delivery address applications because I will need to use them elsewhere on the website.
So my question is. How can I pass this customerId from my login application to my delivery address application without a page reload? Is there a way to access a react application method from outside of that react application?
Login click handler:
clickHandler(e) {
e.preventDefault();
let payload = {"email":this.props.email, "password":this.props.password};
AuthenticationApp.login(payload).then((response) => {
if (this.props.referrer === "checkout") {
$("#checkout_login-register-tab").addClass("done");
$("#checkout_login-delivery-tab").removeClass("todo");
// Temporary hack to reload page until we can pass new user session into delivery/billing react app from here.
location.reload();
}
this.props.updateParentState({isLoggedIn: true});
})
.catch(function(response){
console.log("res2 - catch", response)
});
}
I have forced a page reload here so the delivery address application re-renders and picks up customerId from the service. I need a way to re-render the delivery application at this stage (from this separate application), either by accessing a function somehow or forcing the application to reset? Is this possible or is there a workaround other than a page reload?
You can do something like this:
loadDeliveryApp(data) {
ReactDOM.render(<DeliveryApp data={data} />, document.getElementById('delivery-app'))
ReactDOM.unmountComponentAtNode(document.getElementById("login-app"))
}
clickHandler(e) {
e.preventDefault()
// your login stuff
loadDeliveryApp(data)
}
Here is the working demo.
https://codepen.io/madhurgarg71/pen/dzgxMd
I'm the original creator of React Habitat.. all your applications can talk to each other via either a flux observer or something like redux.
I suggest flux for react habitat normally as the individual stores keep it light weight instead of one BIG store in redux where you might have dead code in a habitat situation.
You will need to create one common store that both your applications import so that if one changes it, the other will get the update. I can put a demo together later if that helps.

HMR strange behavior in Safari with store pattern - Vue2

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.

Categories

Resources