How to use Nuxt Context to call Axios request with param - vue.js

so I'm trying to get my Axios to do a get request with a param that'll end the url in
'/?user= {id}'
the id is passed in by my loggedInUser.id from Vuex. I know that async functions won't accept 'this' inside the call so I included store as a parameter. Something's still off with how I passed the data around thought I think. Would appreciate any help, thanks!
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters(["loggedInUser"])
},
head() {
return {
title: "Actors list"
};
},
components: {
EditProfile
},
async asyncData({ store }) {
try {
const body = { data: store.getters.loggedInUser.id };
const { actors } = await $axios.$get(`/api/v1/actors/`, {
params: {
user: body
}
});
return { actors };
} catch (e) {
return { actors: [] };
}
},
data() {
return {
actors: []
};
Edit
I got it to work when I removed the data: from 'const body' and removed the brackets as well around 'actor'
try {
const body = store.getters.loggedInUser.id;
const actors = await $axios.$get(`/api/v1/actors/`, {
params: {
user: body
}
});

You can access your params from Context.
Context is available in special nuxt lifecycle areas like asyncData, fetch, plugins, middleware and nuxtServerInit.

In Nuxt, with asyncData hook you can get query parameters from the route context key.
Please read the Nuxt.js Context documentation. The context provides additional objects/params from Nuxt to Vue components
With your-domain/?user=wonderman
asyncData({ route: { query: queryParams} }) {},
variable queryParams is an object:
{ user: "wonderman" }

Related

Using XState in Nuxt 3 with asynchronous functions

I am using XState as a state manager for a website I build in Nuxt 3.
Upon loading some states I am using some asynchronous functions outside of the state manager. This looks something like this:
import { createMachine, assign } from "xstate"
// async function
async function fetchData() {
const result = await otherThings()
return result
}
export const myMachine = createMachine({
id : 'machine',
initial: 'loading',
states: {
loading: {
invoke: {
src: async () =>
{
const result = await fetchData()
return new Promise((resolve, reject) => {
if(account != undefined){
resolve('account connected')
}else {
reject('no account connected')
}
})
},
onDone: [ target: 'otherState' ],
onError: [ target: 'loading' ]
}
}
// more stuff ...
}
})
I want to use this state machine over multiple components in Nuxt 3. So I declared it in the index page and then passed the state to the other components to work with it. Like this:
<template>
<OtherStuff :state="state" :send="send"/>
</template>
<script>
import { myMachine } from './states'
import { useMachine } from "#xstate/vue"
export default {
setup(){
const { state, send } = useMachine(myMachine)
return {state, send}
}
}
</script>
And this worked fine in the beginning. But now that I have added asynchronous functions I ran into the following problem. The states in the different components get out of sync. While they are progressing as intended in the index page (going from 'loading' to 'otherState') they just get stuck in 'loading' in the other component. And not in a loop, they simply do not progress.
How can I make sure that the states are synced in all my components?

Vue3 / Vuex State is empty when dispatching action inside of lifecycle hook inside of test

We're using the composition API with Vue 3.
We have a Vuex store that, amongst other things, stores the currentUser.
The currentUser can be null or an object { id: 'user-uuid' }.
We're using Vue Test Utils, and they've documented how to use the store inside of tests when using the Composition API. We're using the store without an injection key, and so they document to do it like so:
import { createStore } from 'vuex'
const store = createStore({
// ...
})
const wrapper = mount(App, {
global: {
provide: {
store: store
},
},
})
I have a component and before it is mounted I want to check if I have an access token and no user currently in the store.
If this is the case, we want to fetch the current user (which is an action).
This looks like so:
setup() {
const tokenService = new TokenService();
const store = useStore();
onBeforeMount(async () => {
if (tokenService.getAccessToken() && !store.state.currentUser) {
await store.dispatch(FETCH_CURRENT_USER);
console.log('User: ', store.state.currentUser);
}
});
}
I then have a test for this that looks like this:
it('should fetch the current user if there is an access token and user does not exist', async () => {
localStorage.setItem('access_token', 'le-token');
await shallowMount(App, {
global: {
provide: {
store
}
}
});
expect(store.state.currentUser).toStrictEqual({ id: 'user-uuid' });
});
The test fails, but interestingly, the console log of the currentUser in state is not empty:
console.log src/App.vue:27
User: { id: 'user-uuid' }
Error: expect(received).toStrictEqual(expected) // deep equality
Expected: {"id": "user-uuid"} Received: null
Despite the test failure, this works in the browser correctly.
Interestingly, if I extract the logic to a method on the component and then call that from within the onBeforeMount hook and use the method in my test, it passes:
setup() {
const tokenService = new TokenService();
const store = useStore();
const rehydrateUserState = async () => {
if (tokenService.getAccessToken() && !store.state.currentUser) {
await store.dispatch(FETCH_CURRENT_USER);
console.log('User: ', store.state.currentUser);
}
};
onBeforeMount(async () => {
await rehydrateUserState();
});
return {
rehydrateUserState
};
}
it('should fetch the current user if there is an access token and user does not exist', async () => {
localStorage.setItem('access_token', 'le-token');
await cmp.vm.rehydrateUserState();
expect(store.state.currentUser).toStrictEqual({ id: 'user-uuid' });
});
Any ideas on why this works when extracted to a method but not when inlined into the onBeforeMount hook?

Passing OAuth token from one Vuejs component to another

I get OAuth token after successful OAuth login in a SuccessOAuth.vue component. I get the token details as follows:
checkforTokens(){
const queryString = this.$route.query;
console.log(queryString);
const token = this.$route.query.accessToken
console.log(token);
const secret = this.$route.query.tokenSecret
console.log(secret);
this.tokens.token = token;
this.tokens.secret = secret;
}
},
beforeMount() {
this.checkforTokens();
}
Now I want to use this token in another component apiCalls.vue where I use this token details to use call the API methods.
<script>
...
methods:{
getProductDetails() {
console.log("==========================================");
console.log(".. Get Product details....");
axios
.get("/auth/getShpDetails", {
params: {
token: this.tokens.token
}
})
.then(response => {
const productInfo = response.data;
console.log("Product info :" + productInfo);
});
},
}
</script>
How do I pass the token details from SuccessOAuth component to apiCalls. I tried using props method but I wasn't able to get the token value to the script tag, not sure about other methods used to pass i.e using $emit and using vuex. Please suggest the best way and the right solution for the problem.
As suggested by #Nishant Sham, I am just modifying the action method in index.js as seen below:
import Vue from 'vue'
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
token: ''
},
getters: {
getToken(state){
return state.token;
}
},
mutations: {
setToken(state, tokenValue){
state.token = tokenValue;
}
},
actions: {
setToken({commit}, tokenValue){
commit("setToken", tokenValue);
}
}
});
In your vue component you call getters and setters as follows:
<script>
//Set token value
var token = "dwe123313e12";//random token value assigned
this.$store.commit("setToken", token);
.....
//Get token value
var getToken = this.$store.getters.getToken;
</script>
You can keep your token inside Localstorage or cookies. And use as per your need. Here is the sample code for this:
const token = 'token'
export function getToken() {
return localStorage.getItem(token)
}
export function setToken(tokenData) {
return localStorage.setItem(token, tokenData)
}
export function removeToken() {
return localStorage.removeItem(token)
}
you can use Vuex for state management. Here is an article
One way to do this could be vuex
in the root, store create a token field and make one getter that you can call from any vue component and on any life cycle hook..
The second way can be that you set the token to localStorage and get/use it wherever you need it
I would prefer the vuex method that way it ensures a single source of truth...
Here is how to use vuex store
First of all install vuex depending on the vue version you are using, Generally, for the vue3 it is advisable to use npm i vuex#next
Create a Store folder inside your src folder and in there add the index.js with the following code
import { createStore } from "vuex";
import axios from "axios"; // I Use axios for making API CALLS hence this pkg
const store = createStore({
state() {
return {
token: null,
};
},
});
export default store;
This is the basic store and state of you app for now.
Lets start adding Actions first because actions are the async code used for making the API call and get the data from server
actions: {
async login(context, payload) {
try {
const result = await axios({
method: "POST",
url: "auth/login",
data: {
email: payload.email,
password: payload.password,
},
});
//If the Request Successed with Status 200
if (result.status === 200) {
//A: Extract the Token
const token = result.data.token;
//B. Token to LocalStorage Optional if you wish to set it to localstorgae
localStorage.setItem("token", token);
//c: UPDATE THE STATE by calling mutation
context.commit("setToken", {
token,
});
}
} catch (err) {
console.log(err);
}
},
},
Next step as you might have guessed adding mutation, which is used for updating your app state..
mutations: {
setToken(state, token) {
state.token = token;
},
},
Last the getter which you shall use to fetch the data either as computed inside your app components this is the
getters: {
getToken(state) {
return state.token;
},
},
Finally after all of this you index.js should look something like this
import { createStore } from "vuex";
import axios from "axios";
const store = createStore({
state() {
return {
token: null,
};
},
actions: {
async login(context, payload) {
try {
const result = await axios({
method: "POST",
url: "auth/login",
data: {
email: payload.email,
password: payload.password,
},
});
//If the Request Successed with Status 200
if (result.status === 200) {
//A: Extract the Token
const token = result.data.token;
//B. Token to LocalStorage Optional if you wish to set it to localstorgae
localStorage.setItem("token", token);
//c: UPDATE THE STATE by calling mutation
context.commit("setToken", {
token,
});
}
} catch (err) {
console.log(err);
}
},
},
mutations: {
setToken(state, token) {
state.token = token;
},
},
getters: {
getToken(state) {
return state.token;
},
},
});
export default store;
NOTE - This is a General representation of how the code for vuex should looks like there are a ton of other way to achive the same result, depending on you project requirment
The above code is not a final code, as it will need to be adjusted as per your test/example/project requirement

VueJS - function in import js file not getting triggered

We are building a web application using Vue JS and PHP, we are new to Vue JS. The server-side execution is fine, the API is able to fetch data as JSON. While trying out a static array display before making the API call, we find that the function in imported "app.js" is not getting called and the table displayed is empty. Please let us know what we might be doing wrong. Appreciate your help.
import Vue from 'vue';
export const MY_CONST = 'Vue.js';
export let memberList = new Vue({
el: '#members',
data: {
members: []
},
mounted: function () {
this.getAllMembers();
},
methods: {
getAllMembers: function () {
/*
axios.get("https://xxxxxx.com/services/api.php")
.then(function (response) {
memberList.members = response.data.members;
});
*/
memberList.members = [{ "empname": "Dinesh Dassss" },
{ "empname": "Kapil Koranne" }];
}
}
});
This is the Vue component. The members object is empty.
<script>
import * as mykey from './app.js'
export default {
name: 'Home',
props: {
msg: String
},
data() {
return {
message: `Hello ${mykey.MY_CONST}!`,
members: mykey.memberList.members
}
}
};
</script>
You can also use this reference for current instance reference:
getAllMembers: function () {
var me = this;
/*
axios.get("https://xxxxxx.com/services/api.php")
.then(function (response) {
// direct this not works here but we have
//saved this in another variable and scope of a var is there
me.members = response.data.members;
});
*/
// this reference works fine here.
this.members = [{ "empname": "Dinesh Dassss" },
{ "empname": "Kapil Koranne" }];
}

Computed Getter causes maximum stack size error

I'm trying to implement the following logic in Nuxt:
Ask user for an ID.
Retrieve a URL that is associated with that ID from an external API
Store the ID/URL (an appointment) in Vuex
Display to the user the rendered URL for their entered ID in an iFrame (retrieved from the Vuex store)
The issue I'm currently stuck with is that the getUrl getter method in the store is called repeatedly until the maximum call stack is exceeded and I can't work out why. It's only called from the computed function in the page, so this implies that the computed function is also being called repeatedly but, again, I can't figure out why.
In my Vuex store index.js I have:
export const state = () => ({
appointments: {}
})
export const mutations = {
SET_APPT: (state, appointment) => {
state.appointments[appointment.id] = appointment.url
}
}
export const actions = {
async setAppointment ({ commit, state }, id) {
try {
let result = await axios.get('https://externalAPI/' + id, {
method: 'GET',
protocol: 'http'
})
return commit('SET_APPT', result.data)
} catch (err) {
console.error(err)
}
}
}
export const getters = {
getUrl: (state, param) => {
return state.appointments[param]
}
}
In my page component I have:
<template>
<div>
<section class="container">
<iframe :src="url"></iframe>
</section>
</div>
</template>
<script>
export default {
computed: {
url: function (){
let url = this.$store.getters['getUrl'](this.$route.params.id)
return url;
}
}
</script>
The setAppointments action is called from a separate component in the page that asks the user for the ID via an onSubmit method:
data() {
return {
appointment: this.appointment ? { ...this.appointment } : {
id: '',
url: '',
},
error: false
}
},
methods: {
onSubmit() {
if(!this.appointment.id){
this.error = true;
}
else{
this.error = false;
this.$store.dispatch("setAppointment", this.appointment.id);
this.$router.push("/search/"+this.appointment.id);
}
}
I'm not 100% sure what was causing the multiple calls. However, as advised in the comments, I've now implemented a selectedAppointment object that I keep up-to-date
I've also created a separate mutation for updating the selectedAppointment object as the user requests different URLs so, if a URL has already been retrieved, I can use this mutation to just switch the selected one.
SET_APPT: (state, appointment) => {
state.appointments = state.appointments ? state.appointments : {}
state.selectedAppointment = appointment.url
state.appointments = { ...state.appointments, [appointment.appointmentNumber]: appointment.url }
},
SET_SELECTED_APPT: (state, appointment) => {
state.selectedAppointment = appointment.url
}
Then the getUrl getter (changed its name to just url) simply looks like:
export const getters = {
url: (state) => {
return state.selectedAppointment
}
}
Thanks for your help guys.