how to get context and env variables in asyncData nuxtjs - vue.js

I'm creating a blog in my nuxt-app that pulls data from contentful ive been following this tutorial, now I can get that all right, but I cant seem to get both context and the environment variables I set up to return from the asyncData argument
I have created a json file like so..
.contentful.json
{
"CTF_BLOG_POST_ITEM": "...",
"CTF_BLOG_POST": "...",
"CTF_SPACE_ID": "...",
"CTF_CDA_ACCESS_TOKEN":"..."
}
and then in my nuxt.config.js
env: {
CTF_SPACE_ID: config.CTF_SPACE_ID,
CTF_CDA_ACCESS_TOKEN: config.CTF_CDA_ACCESS_TOKEN,
CTF_BLOG_POST_ITEM: config.CTF_BLOG_POST_ITEM,
CTF_BLOG_POST: config.CTF_BLOG_POST
}
now basically in my component I've been trying to do this
asyncData(context, {env}) {
return Promise.all([
client.getEntries({
'content_type': env.CTF_BLOG_POST_ITEM,
order: '-sys.createdAt'
})
]).then(([posts]) => {
console.log(context);
return {
posts: posts.items
}
})
},
but when I run this I get cannot read property CTF_BLOG_POST_ITEM of undefined, if I take context out of the arguments this works, and vice versa if I take the {env} I get the context.
How can I get both??
Thanks

The primary (1st) argument of asyncData() is the context object. env is a property of the context object. You could access it as context.env without the use of object restructuring assignment. Your example could be rewritten in the following way without the use of object restructuring assignment:
asyncData(context) {
return Promise.all([
client.getEntries({
'content_type': context.env.CTF_BLOG_POST_ITEM,
order: '-sys.createdAt'
})
]).then(([posts]) => {
console.log(context);
console.log(context.env);
return {
posts: posts.items
}
})
},
The signature asyncData(context, {env}) { is incorrect because you are adding a second argument, {env}, which does not reference the aforementioned context object in any way. If you only need env from context, you can use object restructuring assignment to extract this property in the following way (as you mentioned works when you remove the 1st argument:
asyncData({env}) {
return Promise.all([
client.getEntries({
'content_type': context.env.CTF_BLOG_POST_ITEM,
order: '-sys.createdAt'
})
]).then(([posts]) => {
console.log(context);
console.log(context.env);
return {
posts: posts.items
}
})
},
If you need additional context object properties, using object destructuring assignment. Tt would look like:
asyncData({env, params, req, res}) {
Otherwise, you can just access properties such as context.env, context.params, etc by simply passing context as the first/primary argument with restructuring.
Hopefully that helps!

Related

How to access "this" from Uppy's callback event

I'm using the Uppy Vue component library, and following the docs, I've initialized Uppy by adding it as a computed property.
computed: {
uppy: () => new Uppy({
logger: Uppy.debugLogger
}).use(AwsS3Multipart, {
limit: 4,
companionUrl: '/',
}).on('complete', (result) => {
this.testing = 'success';
console.log('successful files:', result.successful);
console.log('failed files:', result.failed);
}),
}
I'm trying to update my Vue component's data now by using Uppy's complete event, but "this" is not defined. I'm not quite sure how to access "this" from here.
Any idea how to go about doing this?
Update
After posting this, I found a solution that works. I'm hesitant with this solution though as it seemed too easy.
If no one provides a better solution, I'll add this as the answer.
// Uppy Instance
uppy: function() {
return new Uppy({
logger: Uppy.debugLogger
}).use(AwsS3Multipart, {
limit: 4,
companionUrl: '/',
}).on('complete', (result) => {
this.testing = 'success';
console.log('successful files:', result.successful);
console.log('failed files:', result.failed);
})
},
By following the Uppy docs and instantiating the Uppy instance with an arrow function, this no longer seems to refer to the Vue. This makes it so that accessing this.method(), or this.variable, etc. no longer works.
My solution was to change the Uppy instantiation from an arrow function to a regular function. I believe this causes this to refer to the global instance, but I don't have a solid understanding of this, so take what I say with a grain of salt.
I changed this:
computed: {
uppy: () => new Uppy()
}
To this:
computed: {
uppy: function() { return new Uppy() }
}

Vue – access JSON data in method

I'm trying to write a simple function in the methods. It should create an array out of JSON data.
The JSON object is called page with another object called images containing height, orientation etc.
setup () {
return {
page: usePage(),
}
},
methods: {
getOrientations() {
this.page.images.forEach((item) => {
console.log(item
})
}
Unfortunately it throws an undefined is not an object error. Logging only this.page however prints the whole object.
Does this.page.images not work when using it inside a method? Because it works when using it "inline" in HTML.
Thanks for any tips!
You are currently using the Composition API (setup function) alongside the options-based API (methods object). Although it is possible, it is not recommended (take a look at their motivation). Your methods should stand in the setup function:
setup () {
const page = usePage()
page.images.forEach((item) => {
console.log(item)
})
return {
page,
}
},
Of course, you could still modularize this code using functions.
setup() {
// ...
const getOrientations = () => {
page.images.forEach((item) => {
console.log(item)
})
}
// And make it available to your template
return {
// ...
getOrientations
}
}

Retrieve Data from Vuex Store using Axios

I've been searching around for possible answers to my question but I couldn't find anymore suggestions.
The structure of my project is as follows. I have a PoolMainPage where I show some information of the activePool Object. Within the PoolMainPage there are options to direct to subpages (TeamSelector and PoolStandings), which also require information of the activePoolObject.
The activePool Object is fetched from an endpoint in the Vuex store. The code for that is the following:
const actions = {
getActivePool({ commit }) {
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
withCredentials: true
}
const activePoolId = localStorage.getItem('activePoolId')
if (activePoolId) {
return axios.get('/pools/pool-information?poolId=' + activePoolId, config)
.then((response) => {
commit('SET_ACTIVE_POOL', response.data)
return response
})
.catch((error) => {
return Promise.reject(error)
})
} else {
commit('SET_ACTIVE_POOL', null);
return;
}
}
}
The endpoint does it's job and returns the activePool object. In my PoolMainPage component (as well as in the subcomponents) I want to call this action and fetch the object, using:
created() {
if (!this.$store.getters.activePool) {
this.$store.dispatch("getActivePool");
}
},
I added the check to prevent that the endpoint is called everytime the page is refreshed and the activePool is already set. To actually load the activePool into the component, I created a computed property:
computed: {
activePool() {
return this.$store.getters.activePool;
},
},
This all works when the endpoint has returned its data, but before that I receive an error on another computed property, which is dependent on the activePool object:
maxToSpend() {
return this.activePool.inGameBudget;
},
Question 1: how do I make sure that maxToSpend does not compute until the activePool actually set? I can simply add an additional check if (this.activePool) {, but then I would have to do that for all of the computed properties.
Question 2: I don't know if this is possible, but how do make sure that I don't have to add the code to fetch the activePool from the endpoint and get it using the computed property within each of the components that I created: TeamSelector and PoolStandings?
All help/suggestions are appreciated! Let me know if anything is unclear or requires some additional information.
Thanks!
Jeroen
How do I make sure that maxToSpend does not compute until the activePool actually set?
Basically you cannot do that. The computed properties are compute right after component create. See Lifecycle Diagram, computed properties are compute at Init injections & reactivity state.
I can simply add an additional check if (this.activePool) {, but then I would have to do that for all of the computed properties.
You case use Getters:
state: {
...
},
getters: {
maxToSpend: state => {
if (!state.activePool) return
return state.activePool.inGameBudget
}
},
actions: {
...
}
Then you can use as:
computed: {
maxToSpend () {
return this.$store.getters.maxToSpend
}
}
I don't know if this is possible, but how do make sure that I don't have to add the code to fetch the activePool from the endpoint and get it using the computed property within each of the components that I created: TeamSelector and PoolStandings?
Basically no. But if both TeamSelector and PoolStandings have common a parent (might be PoolMainPage?) then you can call it only once from that parent.
In my opinion the way to explicitly dispatch mandatory action for every page its needed it's not a bad idea.
You can mount activePool getter to avoid errors when accessing nested properties. Like:
// in getters.js
const activePool = (state) => ({
...state.activePool,
maxToSpend: state.activePool && state.activePool.inGameBudget,
})
activePool.inGameBudget will always be there, regardless the Promise. So now you can access this.activePool.inGameBudget; in .vue file and you wont get the error.

Make Vue template wait for global object returned by AJAX call

I'm trying to wait for certain strings in a sort of dictionary containing all the text for buttons, sections, labels etc.
I start out by sending a list of default strings to a controller that registers all the strings with my CMS in case those specific values do not already exist. After that I return a new object containing my "dictionaries", but with the correct values for the current language.
I run the call with an event listener that triggers a dispatch() on window.onload, and then add the data to a Vuex module state. I then add it to a computed prop.
computed: {
cartDictionary() {
return this.$store.state.dictionaries.myDictionaries['cart']
}
}
So now here's the problem: In my template i try to get the values from the cartDictionaryprop, which is an array.
<h2 class="checkout-section__header" v-html="cartDictionary['Cart.Heading']"></h2>
But when the component renders, the prop doesn't yet have a value since it's waiting for the AJAX call to finish. And so of course I get a cannot read property of undefined error.
Any ideas on how to work around this? I would like to have the dictionaries accessible through a global object instead of passing everything down through props since it's built using atomic design and it would be insanely tedious.
EDIT:
Adding more code for clarification.
My module:
const dictionaryModule = {
namespaced: true,
state: {
dictionaries: []
},
mutations: {
setDictionaries (state, payload) {
state.dictionaries = payload
}
},
actions: {
getDictionaries ({commit}) {
return new Promise((resolve, reject) => {
Dictionaries.init().then(response => {
commit('setDictionaries', response)
resolve(response)
})
})
}
}
}
My Store:
const store = new Vuex.Store({
modules: {
cart: cartModule,
search: searchModule,
checkout: checkoutModule,
filter: filterModule,
product: productModule,
dictionaries: dictionaryModule
}
})
window.addEventListener('load', function () {
store.dispatch('dictionaries/getDictionaries')
})
I think you can watch cartDictionary and set another data variable.
like this
<h2 class="checkout-section__header" v-html="cartHeading"></h2>
data () {
return {
cartHeading: ''
}
},
watch: {
'cartDictionary': function (after, before) {
if (after) {
this.cartHeading = after
}
}
}
Because this.$store.state.dictionaries.myDictionarie is undefined at the the begining, vuejs can't map myDictionarie['core']. That's why your code is not working.
You can do this also
state: {
dictionaries: {
myDictionaries: {}
}
}
and set the dictionaries key values during resolve.
I also would have liked to see some more of your code, but as i can't comment your questions (you need rep > 50), here it goes...
I have two general suggestions:
Did you setup your action correctly? Mutations are always synchronous while actions allow for asynchronous operations. So, if you http client returns a promise (axios does, for example), you should await the result in your action before calling the respective mutation. See this chapter in the official vuex-docs: https://vuex.vuejs.org/guide/actions.html
You shouldn't be using something like window.onload but use the hooks provided by Vue.js instead. Check this: https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
EDIT: As a third suggestion: Check, whether action and mutation are called properly. If they are handled in their own module, you have to register the module to the state.

accessing attributes in sequelize instanceMethods

I'm adding an instance method to a sequelize model. According to the documentation I should be able to reference this.name, but the only value I can see is this.dataValues.name. I have no reason to believe that the high quality documentation is wrong .... but why does this happen?
Also, there are no setters or getters available. this.getDataValue / this.setDataValue work in getters / setters, but not in instanceMethods.
I can't find any relevant samples on the net - if you know of a project that reads (or better, writes) these values, please add that to your response.
module.exports = (sequelize: Sequelize, DataTypes: DataTypes) => {
return sequelize.define<UserInstance, UserPojo>('User', {
name: { type: DataTypes.STRING }
}, {
instanceMethods: {
myMethod: (value) => {
// this.name is undefined
// this.dataValues.name IS defined ??
}
}, ...
As you can probably see, I'm using Typescript. I just examined the generated Javascript, and immediately saw the problem.
Typescript puts this at the top of the module:
var _this = this;
And references that '_this', rather than the one in the context of the function -- didn't realize that! As soon as I changed this to traditional function() { } syntax, it worked. So, if you're using typescript, you can do this:
myMethod: function(value: type) => void {
}
That is, you don't have to give up on typing your arguments and return value.