Where do I put my server REST logic in vue / vuex - vue.js

In a Vue + Vuex application...
I have list of items. When I create a new item in the client, this item should also be created on the server. The server should supply the ID of the new iem.
So we need a round-trip before we can update the state.
Now, do I put this AJAX call in vuex 'actions' or in vuex 'mutations'? What's some best practices here...

I would add another level of abstraction :
You should split your api calls within another / other files and call these methods from your store actions
//store action
import * as apiService from '../api'
export const someAction = ({ commit }) => {
apiService.getXXX()
.then( result => {
commit(SOME_MUTATION, result)
})
.catch( error => {
commit(SOME_ERROR_MUTATION, error)
})
}

In Vuex, mutations are synchronous transactions, so you'll want to do any asynchronous work (like AJAX calls) using actions.

Related

Firebase authentication and redux-saga synchronizing state

I have a design question on how to manage firebase auth & redux saga states with react-native-firebase.
Example use-case
Let's start from the scenario that I have an app that uses the idToken for a variety of use cases, some in the views using information from the claims, and some in redux actions to make api calls.
Using redux-saga, I would expect to implement these two cases like so:
// in selectors.js
const getIdToken = (state) => state.idTokenResult?.token
const getUserRole = (state) => state.idTokenResult?.claims.role
// in view.js
const role = useSelector(Selectors.getUserRole)
// in actions.js
const idToken = yield select(Selectors.getIdToken)
With this in mind I want to make sure the idTokenResult is available & up to date in my state. I can do this we a few actions and reducers, by calling a login method & then relying on the dispatched event onIdTokenChanged to update my state on login & tokenRefreshes. Something like the following:
// in actions.js
function* onLogin(email, password){
yield call([auth(), 'signInWithEmailAndPassword'], email, password)
}
// This action would be called by an eventChannel which emits on each onIdTokenChanged
function* onIdTokenChanged(user){
yield put({ type: "UPDATE_USER", user: user, })
if (user){
const idTokenResut = yield call([auth().currentUser, 'getIdTokenResult'])
yield put({ type: "UPDATE_ID_TOKEN_RESULT", idTokenResult: idTokenResult, })
}
}
// in reducers.js
const reducer = (state = {}, action) => {
switch (action.type) {
case 'UPDATE_USER':
return { ...state, user: action.user };
case 'UPDATE_ID_TOKEN_RESULT':
return { ...state, idTokenResult: action.idTokenResult }
}
}
Problem
Here is when we run into a problem. I recently learned that the onIdTokenChanged is dispatched lazily, only when the getIdTokenResult() method is invoked link. This means that with the code above we cannot expect our state to be accurate, because when we call yield select(Selectors.getIdToken) it doesn't check getIdTokenResult() and therefore the onIdTokenChanged event is never dispatched.
Potential solutions
How do we overcome this problem?
Set up a timer which periodically calls getIdTokenResult() before the token expires, to trigger the event.
Should work, but defeats the purpose of having an onIdTokenChanged event. Also this means it will refresh the token hourly, even if it isn't needed or being accessed
Somehow call getIdTokenResult() in the selector?
It's an async method so it seems like an anti-pattern here and I'm not even sure it's possible
Use the library directly to fetch user states with auth().currentUser, and forget redux-saga
We lose the nice rerender functionalities that redux's useSelector provides. By accessing the state directly we'll need to figure out another way to trigger rerenders on auth changes, which defeats the purpose of using redux-saga
Something I didn't consider/implemented incorrectly?
Your suggestions are welcome and thanks in advance for you help! :)

Access data from dispatched action using Vuex 4 and Vue 3 Composition API

In my application I have a Vuex 4 store and a Vue 3 Composition Api setup() method.
In the stores action I use axios to make an api call to get a list of bill payments.
The getAllBills action does not live directly in my Store.js file, it exists as a module.
getAllBills({ commit }) {
BillApiCalls.getBills().then(res => {
commit('GET_ALL_BILLS', res.data)
}).catch(error => console.log(error))
},
Then in my Bill.vue file I have the setup() method and am trying to access the data to be used throughout the same Bill.vue file.
setup () {
//Vuex store
const store = useStore();
const billPayments = store.dispatch('payment/getAllBills').then(res => console.log(res));
}
If I check the console from the above .then() res returns as undefined. If I remove the .then() from the billPayments declaration and just do:
console.log(billPayments)
In the console I get
Promise {<pending>}.
Current Store:
import { bill } from './modules/bill.module';
const store = createStore({
modules: {
bill
}
});
The endpoint is working, if I use Postman all of my data is returned as expected but I am having trouble figuring out how to access that data using a dispatched action with the composition api.
The Vuex 4 docs don't mention how to actually resolve the promise to access the data to be used throughout the same component.
An action isn't generally supposed to return data it acts on, data is a part of the state and should be accessed there; this is what another answer shows:
await store.dispatch('payment/getAllBills')
console.log(store.state.payment.bills);
The action doesn't chain promises so it cannot be correctly used. It should be:
return BillApiCalls.getBills()...
Or prefer async..await together with promise to avoid some common mistakes that can be made with raw promises.

Nuxt/Vuejs - How to create utils that have access to modules?

I am using asiox/vuejs to create a webpage. However I want to compartmentalize the code more. One example is I use axios to make requests to the backend, and the data in the response is commited into vuex.
this.$axios.get('events').then((response) => {
this.$store.commit('data/populate', response.data)
})
.catch((e) => {
console.error(e)
})
I want to write a util method for this, like this.$populate.events()
I have tried creating utils inside the plugins/ directory, but they dont have access to this.$axios or this.$store
Note that I have axios and vuex imported in nuxt.config.js
How can this be achieved?
If you need the function in the context, Vue instances and maybe even
in the Vuex store, you can use the inject function, which is the
second parameter of the plugins exported function.
Injecting content into Vue instances works similar to when doing this
in standard Vue apps. The $ will be prepended automatically to the
function.
Reference
export default ({ app, store }, inject) => {
inject("populate", () => {
app.$axios
.get("events")
.then(response => {
store.commit("data/populate", response.data);
})
.catch(e => {
console.error(e);
});
});
};
app variable is context property.
The root Vue instance options that includes all your plugins. For
example, when using axios, you can get access to $axios through
context.app.$axios.
Figured it out not 5 minutes after posting ...
Basically use this nuxt guide
And replace this with app in the method you'd like to move

Validate with asyncData/fetch - Commit data from within Validate?

If I want to validate the existence of a dynamic resource Nuxt recommends implementing a validate() call within the page component. If that call performs an API request for data in order to validate the existence of the resource, Nuxt documentation states that I can check the Vuex store, but does not explicitly state that I can dispatch an action in order to commit data received by the API request. The store within the validate() method allows calls to dispatch and syncs with the Vuex store just as with asyncData or fetch, but would this be an anti-pattern - committing data to Vuex from within validate()?
You can also check some data in your store for example (filled by nuxtServerInit before action
The need to commit data from within validate() will prevent a duplicate API call within asyncData or fetch to the same resource. As an example,
If I call validate() requesting the existence of a resource and I do not commit that resource's data to Vuex, I may need to request that resource data again from with asyncData or fetch.
validate ({ app, params, store }) {
return app.$axios.get('client/cards', {params: { slug: params.card }})
.then(() => true)
.catch(() => false)
}
Essentially duplicating the same API request, but from within the action:
async fetch ({ params, store }) {
await store.dispatch('card/getCard', params.card)
}

Vue - Do API calls belong in Vuex?

I am struggling with finding answer for where to ideally put API calls in vue modules. I am not building an SPA. For example my auth block has several components for login, password reset, account verifiction etc. Each block uses axios for API calls. Axios already provides promises, which are async.
The question is about the best pracitces. Do API calls belong in a Vuex actions? Are there any pros/cons of such approach?
Is there any drawback of keeping axios calls within the components they belong to?
I do API calls in services, not Vuex or components. Basically, mixing the API calls in with the store code is a bit too multi-responsibility, and components should be about providing for the view not fetching data.
As an example of a simple service (using Vue.http but same for an Axios call),
FileService .js
import Vue from 'vue'
export default {
getFileList () {
return Vue.http.get('filelist.txt')
.then(response => {
// massage the response here
return filelist;
})
.catch(err => console.error('getFileList() failed', err) )
},
}
I use it in another service as below (the number of layers is up to you).
Note, the outer service is checking the store to see if the fetch already happened.
DataService.js
import FileService from './file.service'
checkFiles (page) {
const files = store.state.pages.files[page]
if (!files || !files.length) {
return store.dispatch('waitForFetch', {
resource: 'files/' + page,
fetch: () => FileService.getFileList(),
})
} else {
return Promise.resolve() // eslint-disable-line no-undef
}
},
waitForFetch is an action that invokes the fetch function passed in to it (as provided by FileService). It basically provides wrapper services to the fetch, like timeout and dispatching success and failure actions depending on the outcome.
The component never knows about the API result (although it may initiate it), it just waits on data to appear in the store.
As for drawback of just calling the API in the component, it depends on testability, app complexity. and team size.
Testability - can mock out a service in unit tests.
App complexity - can handle timeout / success / failure orthogonally to the API call.
Team size - bigger teams, dividing up the task into smaller bites.