Reactive properties issue with vue.js - vue.js

I have run into a baffling issue with reactive properties in a Vue.js app, which probably results from a misunderstanding on my part and I hope someone can help. In my App.vue, I have import auth from 'src/auth.js', and in my data properties I have this:
data() {
return {
authenticated: auth.user.authenticated,
role: auth.user.role
}
},
This authenticated property is used to show or hide various menu items, e.g:
<li v-if="!authenticated">
<router-link to="/login">Log In</router-link>
</li>
<li v-if="authenticated">Log Out</li>
What I'm finding is that changes to 'auth.user.authenticated' (e.g., switching from false to true after a successful login) are not being reflected in the rendering of the menu items - that is, the authenticated data property is not being updated. To do so, I have to explicitly update it in 'beforeUpdate' and the logout method, with this.authenticated=auth.user.authenticated. At first I thought it was simply that this was assigned on first creation and not subsequently updated and that if I used a computed property instead, this would be dynamic, but that doesn't do the job either. Clearly my App component is unaware of changes to the 'auth' data. How can I make things so that updating is automatic? I have it working at present but the use of 'beforeUpdate' feels like a kludge.

Vue data items are reactive, but non-Vue data items are not reactive. Your code initializes a reactive data item with the value from a non-reactive one.
There is no way for Vue to watch external (to Vue) variables for changes. You will need to notice when your variable changes and tell Vue about it, or just use the external variable (instead of an internal copy) when you need it.

Related

Default-expand-all doesn't work for q-tree? Vue.Js

I have to code a web application and the most important element is the q-tree. I'm already able to load and show data (passing an array called list), but I want that all nodes are expanded.
The vue.js examples of the official documentation show that you're be able to do this with the 'default-expand-all' attribute but this isn't working for me.
It only shows me the root node with an arrow, where I have to expand the children nodes manually.
<q-tree
:nodes="list"
:selected.sync="selected"
#update:selected="onSelectionChangedNode"
node-key="NodeNr"
label-key="NodeTxt"
default-expand-all
></q-tree>
Taking a cue from the accepted answer, I realised that the dom has already been created with the tree component on first render.
In my use case, I want to update the Tree when data comes back from the server.
So, I had to force it to re-render with the expanded functionality using:
this.$nextTick(function () {
this.$refs.nodes.expandAll();
})
The nextTick function will update the dom in the next window of execution, by which time the nodes will get expanded by calling the expandAll function.
And NB: For those confused by the astericks on the ref attribute or how to add it to the component, here goes:
<q-tree :nodes="list"
:selected.sync="selected"
#update:selected="onSelectionChangedNode"
node-key="NodeNr"
label-key="NodeTxt"
ref="nodes"
>
Solved my problem as following:
I have added a ref attribute to the QTree DOM Element which makes it possible to access predefined methods of QTree API.
<q-tree
:nodes="list"
:selected.sync="selected"
#update:selected="onSelectionChangedNode"
node-key="NodeNr"
label-key="NodeTxt"
**ref="nodes"**
>
The function I have been using is expandAll().
updated() {
this.$refs.nodes.expandAll();
}
The most important thing for me was, I had to find out which lifecycle hook was the right one for me. The update() hook was the one I was looking for.
The reason:
Called after a data change causes the virtual DOM to be re-rendered and
patched.
The component’s DOM will have been updated when this hook is called, so you
can perform DOM-dependent operations here.
The default-expand-all is only applied on the first rendering of that Component.
So if your Component renders when the nodes aren't assigned they wont expand if assigned afterwards.
https://v1.quasar-framework.org/vue-components/tree
You have to work with scoped slots and an expanded attribute if you dont have the nodes on first rendering.

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>

Vue - Passing data between dynamic components using eventBus?

I have 3 Components in my 'Search' (parent) View; 'SearchForm', 'ResultList', 'DetailPage'. I switch from the SearchForm to the ResultList when I have received a response from the backend.
<keep-alive>
<component v-bind:is="currentComponent"></component>
</keep-alive>
When a response is recieved in my 'SearchForm' I save it to the searchBus;
searchBus.$emit('searchIssue', response.data);
Then, in my ResultList I want to retrieve it again and display the results;
mounted() {
searchBus.$on(['searchIssue'], (search) => {
this.table_items = search;
});
}
I display a loading animation (also a component) until the response is fully loaded and the ResultList is displayed.
Due to the Vue lifecycle everything is working when all components are displayed in one View, as they are already listening when the bus is updated.
Should I choose a different approach? E.g. using v-show or pass the response back to the Parent and inserting it again with a prop (Idk if it would work as not all components have the same props).
Is there a way to use the Bus anyway ? And how could it be solved making it one linear hierarchy and still hide the non-relevant components? (SearchForm -> ResultList -> DetailPage)
Should I choose a different approach?
I thing that is coming time for using Vuex
At the center of every Vuex application is the store. A "store" is
basically a container that holds your application state. There are two
things that make a Vuex store different from a plain global object:
Vuex stores are reactive. When Vue components retrieve state from it, they will reactively and efficiently update if the store's state
changes.
You cannot directly mutate the store's state. The only way to change a store's state is by explicitly committing mutations. This
ensures every state change leaves a track-able record, and enables
tooling that helps us better understand our applications.

Vuejs - Set "global" prop to edit all properties without using v-model

Assuming that I have this component below:
<c-attachs v-for="item in attachs" v-bind:path="item.path"></c-attachs>
And try to edit some property directly from some method, such as:
methods: {
changeProp: function ()
{
this.path = 'myNewString';
}
}
Vuejs warns on the console with the message:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders
But... if I set "v-bind:allprops="item" and edit property directly through "allprops" object (such as code below), it works fine without error. My doubt is... Is this the correct way to edit property on events without using v-model?
this.allprops.path = 'myNewString';
There is no correct way to edit props, because you are not supposed to edit them.
Every component should have complete control over its own data. That keeps behavior easy to reason about. Items that are passed to children as props should be considered read-only, so that the owner of those items retains control. That is why Vue has events.
When something happens in a component that should affect data that the component doesn't own, the component should issue an event so that the owner of the data item can handle it. If the owner of the data item changes its value, that change will flow down through the props.

Vue.js bind v-model input with prop initial value

I am trying to pass down a initial value to a select input bound by v-model. I cannot figure out why this doesn't work:
props: ['team'],
data() {
form: {
data: {
country: this.team.country
}
}
}
The form.data.country is undefined. Although, the props data is actually passed down. I can access it with Vue Devtools like $vm0.team.country and I can print other data from the props. However, it is not registred in the data().
Also, when trying to debug using mounted(), the property, team, is not defined.
mounted() {
console.log(this.team);
}
But, as I stated earlier, it is defined when the template is rendered, and can be used like this.
<input class="input" type="text" name="name" :value="team.name" disabled>
Why is the properties I am passing not beinged recognized in data()?
The asynchronous loading is not the problem. Even if team is hardcoded above, it's still undefined when data is created in the component. (The component needs to be created before the root Vue that passes the prop.)
There is, perhaps someone will correct me, never a good reason to reference props in your data. Something is either data (the current component knows where to find what it wants) or it's a prop (the component will let it's context supply the info).
Then, as you've discovered, your data is created once. Vue watches everything for changes, but you're vulnerable to changes of the root value. If the thing referenced as a value when data is created, changes, the reactive pipe is broken. The answer to this is the store pattern. You watch a variable in global scope, that never gets replaced, even though it's contents may change.
code
The props data was asynced loaded by the parent. This was suggested in a comment by #Bert