Vue - Do API calls belong in Vuex? - vue.js

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.

Related

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.

Fetching global api data in nuxt

I'm starting Nuxt project and I have to fetch basic data from my backend.
for example language dictionary or some data for leftbar and etc.
in Vue I can just call everything I need in App.vue and save it in vuex and then use whenever I want, but with Nuxt every page is different and I'm curious where should i call this global data api calls.
I'm guessing I have to create middleware and call actions if data is not loaded already or there is better solution?
You mention it's global data and you're using universal mode- I think you're looking for nuxtServerInit(). This store action runs once on the server, before created, mounted hooks etc. You can use it to populate your store with data that your components (including pages) rely on.
Take a look at the docs.
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user) {
commit('user', req.session.user)
}
}
}

Intercept Axios request with global JS methods

I have a site with web components based architecture, where each web component may be a separate Vue app with it's own API layer integrated via Axios. I need to implement Auth middleware for all HTTP requests, coming from either root app or web component app. I cannot use Axios built-in interceptors mechanism as there will be multiple instances of Axios. Is there a way I can do it with global JS methods? I know there is some browser extension based API out there, but that doesn't seem like something I am looking for.
Just in case anybody else is interested, I have solved it with service worker. You can subscribe to fetch events and respond according to your auth logics. Your service worker code will look something like following:
self.addEventListener('fetch', async (event) => {
const isAuthorised = await checkIsAuthorized(); // your auth API layer
if (!isAuthorised) {
const response = new Response(null, {
status: 401,
statusText: 'Unauthorised',
});
event.respondWith(response);
return;
}
event.respondWith(fetch(event.request));
});
Service worker is able to intercept axios requests from shadow DOM as well, so it's a good match for web components case.
Besides, there is a nice article by Bartosz Polnik on implementing auth layer using service worker.

How do MongoDB Stitch SDK's work in regards to client instantiation or how does Stitch.defaultAppClient.getServiceClient work?

I'm using expo to build out a React Native application and I'm running into issues when attempting to write code that accesses remote MongoDB servers. I'm attempting to use MongoDB's provided Stitch SDK's for React Native.
When running
const mongoClient = Stitch.defaultAppClient.getServiceClient(RemoteMongoClient.factory, "mongodb-atlas");
I'm running into the following error:StitchServiceError: service not found: 'mongodb-atlas'
When my app initializes in my main App component, I'm initializing the default client using Stitch.initializeDefaultAppClient per the recommended documentation. Based on my debugging logs, this part is working correctly and I'm able to authenticate with the service correctly and I am storing the client in the App component's state. I'm running the loadClient method in the constructor of my main App component.
_loadClient() {
console.log("Loading Stitch client");
Stitch.initializeDefaultAppClient("xxxxxxxxxxxxxxx").then(client => {
this.setState({ client });
this.state.client.auth
.loginWithCredential(new AnonymousCredential())
.then(user => {
console.log(`Successfully logged in as user ${user.id}`);
this.setState({ currentUserId: user.id });
this.setState({ currentUserId: client.auth.user.id });
})
.catch(err => {
console.log(`Failed to log in anonymously: ${err}`);
this.setState({ currentUserId: undefined });
});
});
}
For more context: I'm executing the getServiceClient function in a separate react saga so that I can fetch data behind the scenes based on actions that are dispatched within the application. I'm calling getServiceClient inside a function that gets called upon every dispatch of a specific action. All of this is exported to a single async function which is then applied as saga middleware enhancer to a store.
I think I'm not able to retrieve the service client because the defaultappclient isn't initialized within the context of the saga because of the way sagas work (from my understanding) but I need more insight into how getServiceClient() works.
I ended up storing the client in a local instance in the saga js file so that the instance is available for all sagas and I plan on keeping all sagas within this file. I am using asynchronous functions to ensure that the app client is initialized prior to binding any client requests to redux actions.
Example:
let appClient;
function* initAppClient() {
console.log("Initializing Stitch Client");
yield Stitch.initializeDefaultAppClient("client-identification-here ").then(client => appClient=client);
}
export default function* rootSaga() {
yield initAppClient();
yield takeEvery('ACTION HERE', uploadState);
}
The downside to this approach is that this instance won't be available to the rest of my react application and I won't be able to use Stitch functionality to update anything through the actual react application. This works for me as I only plan on using Stitch when state changes within my application and this decouples any server/remote data operations from react application functionality which focuses on presentation, routing, etc. If I want to use Stitch within my react application, I would have to initialize another client within react's context.

Issue using redux variables via redux call inside react-apollo graphql with apollo compose

My Component needs to call an graphql query but it needs variables which can be only used by redux. So I have mapped the redux's connect to react apollo compose. But I have issues getting the data from reducer and the call is not loading properly.
class abc extends Component {
componentWillMount() {
console.log(this.props.variableData); // Getting empty object.
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ variableDataFetchAction }, dispatch);
}
function mapStateToProps(state) {
return {
variableData: state.reducerName.variableData,
}
}
export default compose(
connect(mapStateToProps, mapDispatchToProps),
graphql(MyQuery, {
options: props => {
return {
.....,
variables: props.variableData,
};
},
}),
)(abc);
I need to know whether there is problem in the way in which I can redux. Because my store seems to empty.
Just to be clear, you don't need react-apollo to call a GraphQL api endpoint.
In your case, you're utilizing 2 library that handle it's own store (assuming you're using Apollo Client 2). Redux has it's Redux Store and Apollo is using InMemoryCache for caching.
Stuff you fetched via graphql are stored in Apollo cache, you can use Apollo Client Developer Tools Chrome extensions to inspect it's content.
If you want to put the data fetch via apollo-client back to Redux store, you have to manually dispatch an action to do it (once the data return from graphql calls). This is mostly undocumented since it's not recommended to use 2 types of Store.
If all you need is to call a GraphQL endpoint and not changing your Redux implementation. I suggest using apollo-fetch or graphql-request instead.