I am experimenting with Vue and VueX and while everything is working well, there is one aspect that is troubling with regards to the store mechanism.
I have a component that loads a set of data from a remote service via axios. It works correctly and is called when the component is created.
export default {
created() {
this.$store.dispatch('foo/getBar');
}
...
}
This correctly populates the "bar" variable in the component with the valeus returned from the api call.
When I next view the component in the application, the created function is called again and the api called again, which returns the same data.
What is the best practice way of avoiding subsequent calls until we know that there is different data to be collected? Or more precisely, how do I invalidate data in the store when necessary so that api call is made only when it needs it?
You can put your api call to the parent or root component then place a refresh button to the child component.
Or you can check if variable bar is empty then make the api call.
Related
In options api,its obvious where we write code for fetching the data from server in mounted method.
With composition api,i am confused as the setup method is the one that loads first before the onMounted hook.
Check their docs: https://vuejs.org/guide/essentials/lifecycle.html#lifecycle-diagram
If you are doing DOM related actions, you would want to do it in onMounted() hooks, because setup() doesn't have access to DOM yet.
So I would probably do it in onMounted() methods since I would probably store result from API to component data or may update DOM as a side-effect.
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.
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>
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.
I'm using Relay with React Native and have a problem during login & logout.
After login or logout, Relay keeps the store from the previous user. To solve this I use Relay.Renderer and Relay.Environment. As in, in each Renderer I put singleton object of Environment.
The problem is that I previously did a mutation on object of Relay.Store, as in
Relay.Store.commitUpdate(new CreateProfile(), callback).
Now it doesn't work. I guess this is because Relay.Store doesn't know anything about server endpoints. But Relay.Environment does.
And now I'm using something like this this.props.relay.commitUpdate(new CreateProfile(), callback). It works pretty well when the parent component is wrapped as Relay.Container, so it has relay object in props.
But what should I do in components which are not Relay.Containers and don't have Relay object in props?
Relay.Store is a globally accessible singleton instance of Relay.Environment and Relay.Store.commitUpdate() updates data in that global environment. But since you're using your own instance of Relay.Environment, to update it you need to use this.props.relay.commitUpdate(), as you noted. This updates the environment the container was rendered with.
If need to make mutations from child components of containers, that are not wrapped in a Relay.Container, there are two ways to do that. You could simply pass the relay prop to them, so in the render function of your container you would have:
<Child relay={this.props.relay} />
However, since those plain components are not in a Relay container, they don't currently need to know anything about Relay. If you want to keep them that way, you could write the method that does the update in your container component like this:
onCreateProfile = () => {
this.props.relay.commitUpdate(new CreateProfile());
};
and only pass a callback to your child component in render:
<Child onCreateProfile={this.onCreateProfile} />
If you need to make a mutation from a component that does not have a Relay.Container above it in the component hierarchy at all, you could create the Relay.Environment in a shared root component higher up and pass it down using props (or pass a callback using the strategy shown above).