Vuex: Difference between using an Action vs. handling within component? - vue.js

I am confused at why actions in Vuex can't just be handled within a component.
Assuming I have a basic store:
store.js
const initialState = () => ({
overlayText: ''
})
const mutations = {
setOverlayText: (state, payload) => {
state.overlayText = payload;
},
}
const actions = {
clearOverlay: (context, data) => {
return axios.get(data.url).then((response) => {
context.commit('setOverlayText', response);
});
},
}
If I want to make an API call and change data based off it like below using a Vuex Action:
Option 1
<button #click="dispatchClearOverlay">Get Data</button>
methods: {
clearOverlay() {
this.$store.dispatch('clearOverlay', {
url: '/api/clear-overlay',
})
}
}
what is the difference of just doing it within the component like this?
Option 2
<button #click="clearOverlay">Get Data</button>
methods: {
clearOverlay() {
axios.get('api/clear-overlay')
.then(resp => {
this.$store.commit('setOverlayText', response);
})
}
}

The examples you gave are slightly different in that in Option 1, the only possible value that will get stored in state.overlayText is the response from /api/clear-overlay. But in Option 2, you could pass any arbitrary text when you commit the mutation and that value would be stored in state.overlayText.
More generally, there are some important differences. Mutations have to be synchronous and Actions can be asynchronous. You can also fire multiple mutations by dispatching a single action (imagine if you frequently needed to call the same three mutations). These two features can help keep your components nice and lean, while centralizing more of the Store logic.
The Dispatching Actions section of the Actions docs helps illustrate these points.

Related

vuex ajax call and several components

I'd like to know whats the best practice for an ajax call with vuex and multiple components sharing the data from this call.
Do I store a loading, success and error property for that call in the store so it populates all the way through to the components? I'm not sure whats the best way. I also don't want to call the ajax from all the components since that defeats the purpose of having it called once.
Right now I'm storing as I mentioned above:
new Vuex.Store({
state: {
persons: {
data: null,
loading: false,
error: null
}
}
});
I do this for EACH api call and then I store it there. Any better ways?
This article describes a few patterns for implementing ajax calls and updating vuex state. Personally I agree with the author that method 3 of moving your ajax calls to the vuex store as actions because, as the article points out, it decouples state and presentation logic. This is also helpful because you can return a promise from your action to know when the state can be mutated
Code example from the article:
store = new Vuex.Store({
state: {
message: ''
},
mutations: {
updateMessage(state, payload) {
state.message = payload
}
},
actions: {
refreshMessage(context) {
return new Promise((resolve) => {
this.$http.get('...').then((response) => {
context.commit('updateMessage', response.data.message);
resolve();
});
});
}
}
});
Vue.component('my-component', {
template: '<div>{{ message }}</div>',
methods: {
refreshMessage() {
this.$store.dispatch('refeshMessage').then(() => {
// do stuff
});
}
},
computed: {
message: { return this.$store.state.message; }
}
});
So as you can see here, ajax calls are encapsulated as vuex actions. In a component that relies on data from an ajax call, the component can perform the action (this.$store.dispatch('refreshMessage').then...) which will make the ajax call and update the data in the store. Since your component already has a reactive property that depends on data from the store, it will update automatically once the store has new data.

How can my vuex mutation dispatch a new action?

How can my vuex mutation dispatch a new action or how can my action get read access to the store?
Basically I've got a action that calls an mutation:
updateSelectedItems: (context, payload) => {
context.commit('updateSelectedItems', payload);
},
And the mutation that updates the list. It also gets any new items. I need to do something with these new items:
updateSelectedItems: (state, payload) => {
var newItems = _.differenceWith(payload, state.selectedItems, function (a, b) {
return a.name === b.name;
});
state.selectedItems = _.cloneDeep(payload);
_.each(newItems, (item) => {
// How do I do this??
context.dispatch('getItemDetail', item.name)
});
},
It's really not best practice to make your mutations do too much. It's best if they're super-simple and generally do one thing. Let your actions take care of any multi-step processes that might affect the state.
Your example would make more sense structured like this:
actions: {
updateSelectedItems(context, payload) {
var selectedItems = context.state.selectedItems;
var newItems = _.differenceWith(payload, selectedItems, (a, b) => {
return a.name === b.name;
});
context.commit('setSelectedItems', payload);
_.each(newItems, (item) => {
context.dispatch('getItemDetail', item.name)
});
},
getItemDetail(context, payload) {
// ...
}
},
mutations: {
setSelectedItems(state, payload) {
state.selectedItems = _.cloneDeep(payload);
}
}
If you really need to dispatch something from inside a mutation (which I'd highly recommend not doing), you can pass the dispatch function to the mutation as part of the payload.
It is technically possible using this to call dispatch or commit (or few others) ... i'm only mentioning this for anybody who comes here and needs it for their specific use case.
In my situation i'm using fiery-vuex library which actually passes a function as the payload that will return the updated data, i use this along with a refresh_time key in the db to determine when to refresh certain user data
SOME_MUTATION( state, getData() ){
const new_data = getData()
if( new_data.refresh_time > state.user.refresh_time ){
this.dispatch( 'refreshFromOtherStateMeta', state.user.id )
}
state.user = new_data
}

Why is it considered poor practice to use Axios or HTTP calls in components?

In this article, it says:
While it’s generally poor practice, you can use Axios directly in your components to fetch data from a method, lifecycle hook, or whenever.
I am wondering why? I usually use lifecycle hooks a lot to fetch data (especially from created()). Where should we write the request calls?
Writing API methods directly in components increases code lines and make difficult to read.
As far as I believe the author is suggesting to separate API methods into a Service.
Let's take a case where you have to fetch top posts and operate on data. If you do that in component it is not re-usable, you have to duplicate it in other components where ever you want to use it.
export default {
data: () => ({
top: [],
errors: []
}),
// Fetches posts when the component is created.
created() {
axios.get(`http://jsonplaceholder.typicode.com/posts/top`)
.then(response => {
// flattening the response
this.top = response.data.map(item => {
title: item.title,
timestamp: item.timestamp,
author: item.author
})
})
.catch(e => {
this.errors.push(e)
})
}
}
So when you need to fetch top post in another component you have to duplicate the code.
Now let's put API methods in a Service.
api.js file
const fetchTopPosts = function() {
return axios.get(`http://jsonplaceholder.typicode.com/posts/top`)
.then(response => {
// flattening the response
this.top = response.data.map(item => {
title: item.title,
timestamp: item.timestamp,
author: item.author
})
}) // you can also make a chain.
}
export default {
fetchTopPosts: fetchTopPosts
}
So you use the above API methods in any components you wish.
After this:
import API from 'path_to_api.js_file'
export default {
data: () => ({
top: [],
errors: []
}),
// Fetches posts when the component is created.
created() {
API.fetchTopPosts().then(top => {
this.top = top
})
.catch(e => {
this.errors.push(e)
})
}
}
It's fine for small apps or widgets, but in a real SPA, it's better to abstract away your API into its own module, and if you use vuex, to use actions to call that api module.
Your component should not be concerned with how and from where its data is coming. The component is responsible for UI, not AJAX.
import api from './api.js'
created() {
api.getUsers().then( users => {
this.users = users
})
}
// vs.
created() {
axios.get('/users').then({ data }=> {
this.users = data
})
}
In the above example, your "axios-free" code is not really much shorter, but imagine what you could potentially keep out of the component:
handling HTTP errors, e.g. retrying
pre-formatting data from the server so it fits your component
header configuration (content-type, access token ...)
creating FormData for POSTing e.g. image files
the list can get long. all of that doesn't belong into the component because it has nothing to do with the view. The view only needs the resulting data or error message.
It also means that you can test your components and api independently.

Calling two mutations from the same action

I'm using a Vuex store to keep all the items in a shopping cart.
There's two actions on the store :
getCartContent, which gets called on page load (fetches the initial content from the backend, which in turn retrieves the data from the session)
addToCart, which is dispatched by the <Products> component when the user clicks the add to cart button.
Both of these call a respective mutation (with the same name), since you're not supposed to call mutations directly from within components.
Here is what the store looks like :
const store = new Vuex.Store({
state: {
items: [],
},
mutations: {
getCartContent(state, data){
axios.get('/api/cart').then(response => {
state.items = response.data;
});
},
addToCart(state, data){
axios.post('/api/cart/add', {
item_id: data.item,
});
}
},
actions: {
getCartContent(context){
context.commit('getCartContent');
},
addToCart(context, data){
context.commit('addToCart', {item: data.item});
}
}
});
This is working as expected, but now when an item is added to the cart (with a dispatch to the addToCart action from within the component), I would like it to call the getCartContent mutation just after so that it fetches a fresh list of items from the backend.
I tried commiting the second mutation from the same action, like this :
actions: {
// ...
addToCart(context, data){
context.commit('addToCart', {item: data.item});
context.commit('getCartContent');
}
}
But that doesn't always work, sometimes it will fetch the items but not always.
I also tried dispatching the getCartContent action from within the component itself, right after dispatching the addToCart action, but it's the same problem.
How can I solve this?
Your axios calls are asynchronous, meaning that your addToCart mutation might not necessarily be finished when your getCartContent mutation fires. So, it's not surprising that sometimes getCartContent doesn't return the items you told axios to send a post request for immediately prior.
You should move asynchronous calls to the vuex actions:
actions: {
getCartContent(context, data) {
axios.get('/api/cart').then(response => {
state.items = response.data;
context.commit('getCartContent', response.data),
});
},
addToCart(context, data) {
axios.post('/api/cart/add', {
item_id: data.item,
}).then(() => {
context.commit('addToCart', data.item)
})
},
}
And your mutations should do nothing but make simple, straight-forward changes to the module state:
mutations: {
getCartContent(state, items) {
state.items = items;
},
addToCart(state, item) {
state.items.push(item);
}
}
The above explanation assumes that instead of making a get('/api/cart') request after each POST request, you would just keep track of items by pushing the data to the state.items property.
If however, you really want to make the GET request after adding an item, you can just get rid of the addToCart mutation and dispatch the getCartContent action after the POST request finishes:
addToCart(context, data) {
axios.post('/api/cart/add', {
item_id: data.item,
}).then(() => {
context.dispatch('getCartContent');
})
},

Vuejs simplify vuex action call

I am calling a store action through a component like:
sublitPost(){
this.$store.dispatch({type:"submitPost", userPost:this.newPost});
}
and the store action looks like this:
actions: {
submitPost({commit, state}, userPost: any) {
/...rest code here
Although, I would like to simplify this and call the action like:
sublitPost(){
this.$store.dispatch("submitPost", this.newPost);
}
What is the tweak in the action's signature I have to do? What I've tried is to have the action like:
actions: {
submitPost(userPost: any) {
console.log({userPost})
/...rest code here
but in this case, the received object doesn't look correct. What I get in the console log is:
Any help is welcome
You should change your syntax:
actions: {
submitPost(state, data) {
console.log(data)
{
I have divided my store into modules so I am exporting my all mutations, actions, getters.
I am committing a mutation from my action with payload,here payload is an object with property currentWidth
export function fetchDeviceCurrentWidth ({ commit, state }, payload) {
commit('updateDeviceCurrentWidth', payload)
}
My mutation
export function updateDeviceCurrentWidth (state, payload) {
state.currentDeviceWidth = payload.currentWidth
}
My getter
export const currentDeviceWidth = state => state.currentDeviceWidth
This is from where I am dispatching an action:
myEventHandler () {
this.$store.dispatch('fetchDeviceCurrentWidth', {
currentWidth: window.innerWidth
})
}
There are many ways to use vuex store, but I like to break store into modules depending on features or Components.