Good practice for modifing vuex on initial app load - vue.js

Introduction:
Currently the application has multiple modules that store data, for example:
- profile
- models
- products
- etc
... later components under the different routes reuse and modify store data.
The problem:
When the application is initially loaded (no matter what route, or component) it's needed that certain logic has to be executed in order to set the needed state of store.
Simple example can be:
Depending on the user's age in the profile:
1. Find a certain model in models
2. And update profile data with the values from model
There are methods like created() or mounted() during component creation, so it made me think about some sort of representational container under the parent route. But I wonder maybe there are different sort of hooks to be added on the initial application load.

You usually feed your initial data into the store from another (persistent) data storage. This can be LocalStorage or an external source (an REST API for instance).
One way of doing this is too postpone app creation until the store is populated and then proceed with app init.
You init code in main.js will look something similar to this
import store from './store'
someAsyncTask()
.then( () => {
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
})
This means that the user needs to wait until everything is loaded so presenting a static preloader (usually added in index.html) is a good option.

The solution for my problem ended up very obvious, but initially escaped my mind. Since the App (root component) is being passed to the Vue instance, all logic required for manipulating can be actually executed there during created or mounted methods.
However if you actually rely on the AJAX calls to be resolved before initialising the app the Radu Dita approach should be taken into the consideration.

Related

Nuxt (SSR/Vuex): Dispatch an action once all of the components have been created

Consider the following Widget component.
<template>
<component :is="name" />
</template>
<script>
import { mapActions } from 'vuex';
export default {
props: {
name: {
type: String,
required: true
}
},
created() {
this.addWidget(this.name);
},
methods: {
...mapActions({
addWidget: 'widgets/add'
}) // adds widget name to an array
}
};
</script>
I want to have multiple components like this one all over the page.
I need to be able to gather all of the component names so I can fetch the relevant data for them.
My current idea is to add the component name to the store when the created hook fires.
That works just fine, but my problem is that I can't find a way to dispatch a Vuex action to fetch the data once all of the components have been created. I tried using various hooks to no avail since none of them seems to have Vuex access.
Am I missing something? Is there some kind of hook that fires after all of the components have been created (SSR) that has Vuex access. Or maybe there's some better way to achieve what I've been trying to do?
Why do you want to wait until all the widgets have been loaded to fetch the data? Instead, what I'd do is fetch the data for each Component as they get added in the page. Using this approach, each component would require a specific piece of data, and nothing better than to load each of them in a different request. Easier to test, easier to trace, adheres to the single responsibility principle.
If you are worried that two components may require the same data, then you can route all requests through an Object that hashes the request (endpoint + verb if using REST) and keeps a pool of active connections, so that if two requests come in one reuses the Promise of the other, and only one of them reaches the server.
Now, if you really want to do it using your original approach, make it so that each widget emits an event once it's been registered. The events are collected by the parent component (maybe the Page that loads all the widgets). The page knows which Widgets are being loaded. Every time an event is received it sets a flag on that specific widget to indicate it's been loaded. Once all widgets get a flag, the Page triggers a request to fetch the data.

Issues with data method & context within Vue

I am getting some strange behavior in my Vue application that I am developing.
In my view I define my data initially:
...
data() {
return {
organization: {
selectedOption: null,
options: [],
},
};
},
...
Intention is to populate this via a call to my backend API, which I do using => notation via axios:
// The following snippet is in my methods:
...
axios.get('http://localhost:8000/api/org/types')
.then((response) => {
Object.keys(response.data).forEach((k) => {
this.organization.options.push({
value: k,
text: response.data[k],
});
});
this.organization.selectedOption = this.organization.options[0].value;
});
...
The data comes in, and I can see it indeed does set the values until I go elsewhere within the view.
I initially called the method above in the beforeMount() method however I moved it to the created() method (due to data context/reactivity matters) and all seemed to be working just fine.
Now I am having an issue where when accessing the data where it is always seemingly set to initial data I have defined. I am verifying this via debug/console.
Within mounted():
console.log(this.organization); // Returns observer that shows the data I would expect to be there via Console, but initial data when accessing anything.
console.log(this.organization.selectedOption); // Returns null
Is there something I am not understanding how the Vue data method works? I was under the assumption that after the context has been created the underlying data can then be mutated for the life-cycle of that view.
EDIT:
I did attempt to return the promise on the axios call, but to no avail.
There are a couple of keys things to note here.
Firstly, when you log an object to the console it is live. You'll probably see a little blue 'i' icon after you expand the object that explains this. What this means is that the object properties are not copied. Instead the console just has a reference to the object. It only grabs the property values when you click on the object in the console to expand it. You can work around this by logging out console.log(JSON.stringify(this.organization)) instead.
The second point to note is that it really doesn't matter which hook you use to load the data. The hooks beforeCreate, created, beforeMount and mounted will all run synchronously at the relevant stages. Meanwhile, your data is being loaded asynchronously. Vue won't wait for it, there's no support for that. No matter which hook you use the data won't be loaded until after the initial rendering/mounting is complete. This is a common problem and you just need to write your component in such a way that it can cope with the data being missing when it first renders.
To be clear, I'm not saying that the hooks are interchangeable in general. They most definitely aren't. It's just that when you're loading data using an AJAX request it doesn't make any real difference which you use. The AJAX request will always come back after all of those hooks have been run. So performing the request in an earlier hook won't make the data available in the later hooks.
A common alternative is to load data in a parent component and only create the child once the data is loaded. That's usually implemented using a v-if and the data is then passed using a prop. In that scenario the child doesn't have to deal with the data being missing.

load routes via component from external api and add them to the router

I would like to load my routes from an external API. Some users might not have the permissions to access a module.
So my navbar makes an API call and gets all the modules returned. These module objects contain the path to the view file.
I tried to create a small sandbox to reproduce the problem
https://codesandbox.io/s/vue-routing-example-i5z1h
If you open this url in your browser
https://i5z1h.codesandbox.io/#/First
you will first get the following error
Url /First not found
but after clicking on the First module link in the navbar, the First view should get rendered.
I think the problem is related to the fact that the page has not yet started the navigation created event after loading and the module page is therefore not found. After changing a router URL the navigation component had enough time to add all the required routes to the router.
How can I load these URLs before the router leads to the first route and responds a 404 error?
The key idea here is to load the routes asynchronously which means you must defer loading of your SPA till that time. In your index.js or main.js, your code would be something like this:
// Some functions are assumed and not defined in the below code.
import Vue from 'vue';
import VueRouter from 'vue-router';
// Application root component
import App from './App.vue';
import { getRoutes } from './api';
// Register Vue plugins
Vue.use(VueRouter);
// Make API call here
// Some animation before the app is fully rendered.
showLoader();
getRoutes(/* Optional User data */)
.then((routesData) => {
// Stop the animation
stopLoader();
return routesData;
})
.then((routesData) => {
// processRoutes returns an array of `RouteConfig`
const routes = processRoutes(routesData);
const router = new Router({
routes: [
...routes,
{
path: '*',
component: NotFound
}
]
});
})
.then((router) => {
const app = new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
});
});
Additionally, there are a few things you need to do:
Routing is generally the higher-level concern. So if you consider DIP - Dependency Inversion and the stateful + singleton nature of the router, then it makes sense to bootstrap it at the very beginning. Thus, anything that router needs should be available. This means that the navbar component should not be responsible for making the API call. You must take it out.
Another possible solution is to use $router.addRoutes() method. But it is inadequate for your needs. It will not work considering authorization in mind. It will not prevent navigation.
On a philosophical level, when you are using SPA with client-side routing, then client-side routing is its own source of truth. It is reasonable to know all the routes upfront and hence most routers are designed with this idea in mind. Thus, a requirement like this is a poor fit for this paradigm. If you need something like this, then a server should possess the knowledge of client-side routes and during page refresh, the server should decide what to do - Load the SPA or reject with 404/403 page. And if the access is allowed, the server should inject routing data in the HTML page which will then be picked by Vue.js on the browser side. Many sophisticated SSR - Server-Side Rendering techniques exist to achieve this.
Alternative strategy: Use guards
Define all the routes upfront in your router for all the possible views of all the users.
Define guards for each authorized routes. All these guards would be resolved asynchronously.
Instead of loading routing data from API, use the API to return an Authorization Matrix. Use this API response in your route guards to determine the access.
To prevent calls to the same API multiple times, you can use some sort of caching like Proxy, Memoization, store etc. Generally, for a user, the Auth Matrix will not vary between the calls.
As an advantage of this, you can still load the application partially if required leading to meaningful user experience by reducing the user's time to interact with the application.

How To Ensure Reference Data Is Loaded In Vue?

I have webpack setup to bundle all of the source. I have a Vue object that is the page and multiple Vue components of my own creation. This works great!
I am now getting reference data from the database to fill in certain default options for some of these components. In my pages Mounted or Created events (there is no difference for my question) I am calling a method that will check to see if the data exists in localStorage and if not, it will extract the data from the database.
Once Extracted, I have it in localStorage so it is not an issue. However, the first time I need to gather the data (or when I need to refresh it because I have another trigger that lets me know when it has changed) the page and components have rendered (with errors because of lack of data) before the data comes back. The fetch method is in a promise, but mounted events don't seem to care if a promise exists within in before it continues to the next component.
So what is the best practice for loading/refreshing reference data in Vue? I am currently not using VueX because this is not a SPA. Sure, it is a single page that is doing things (there are many single pages that do their own thing in this site) but I have no need to make it a full SPA here. But If VueX and its store will give me some sort of guarantee that it will occur first or page/components will run AFTER VueX things, I will learn it.
Have you tried doing so:
<component v-if="page.isDataLoaded">...</component>
in your Vue-component:
data() {
return {
page: {
isDataLoaded: false,
}
}
},
mounted() {
this.fetchPageData().then(() => this.page.isDataLoaded = true);
}
You can use v-if and v-else to show, for example page loader element like so:
<PageLoader v-if="!page.isDataLoaded"></PageLoader>
<component v-else>...</component>

Which approach to storing loading processes is preferable?

Can You please help to decide which one of the architectures will be better
(fetching list from API with with react-native, react-redux, redux-thunk)
example
// component
componentDidMount() {
this.props.dispatch(fetchFunction());
}
// thunk action
fetchFunction () {
dispatch START_LOADING
return dispatch (
fetch()
dispatch SUCCESS_LOADING
);
}
OR
example
// component
componentDidMount() {
this.setState({'loading': true})
this.props.dispatch(fetchFunction()).finally(
this.setState({'loading': false})
);
}
//thunk action
fetchFunction () {
return dispatch (
fetch()
dispatch SUCCESS_LOADING
);
}
My idea is about storing "loading process" in local components state? What are bad and good sides of this approaches?
as i see it - example 2:
If loading takes longer time, i can leave component (it gets unmounted) and i will see warning - "changing state on unmounted component"
1 example:
Saves a lot of extra data that i do not need in redux store (also a lot of data i need to exclude from persist), and for example if i have a web store product component, i will have a lot of UI props stored in redux (for example list with 10 products, and each product has it's own buttons with states) :(
BUT i like it because all the logic stays in redux-thunk-actions, and i do not need to care in component how to continue coding after dispatch, whether it was a promise or wasn't example:
this.props.dispatch(fetchFunction()).then(() => {});
or just
this.props.dispatch(fetchFunction());
.. some code
So far I've made simple projects, and both ways worked fine.
Can you please give some advice which way to go for the bigger project?
If components outside of this component's tree need to know about the loading state, then keep the loading state in the store so that those outside components can query that state.
In a large app, whether a components need to know of a given piece of state is something can changes; starting out, Component X has local state because no other components need to know about local state X, but as the app grows, new components, thunks, etc are introduced that do need to know about local state X, and it has to be moved to the store. So to address this, one approach is to put all state in Redux from the beginning so you don't have to worry about unforeseen refactoring of local to global state.
Keeping everything in the redux store also makes debugging easier because you can see all the values in redux devtools rather than having to find individual components to see their local state.