So basically i want to input a keyword after that i will send an api request and after I receive the results I will push it to my movies(object) then loop through the results.
This is my code
<script>
import MovieCard from '~/components/MovieCard.vue'
import _ from 'lodash';
import axios from 'axios';
export default {
name: 'Navbar',
components:{
MovieCard
},
data () {
return {
search: false,
input: '',
movies: {},
}
},
methods:{
searchMovies: _.debounce((e) => {
axios.get('https://api.themoviedb.org/3/search/movie?api_key=123456789&query='+e.target.value)
.then(response => {
this.movies.push(response.data.results);
})
}, 2000)
}
}
</script>
And this is from my form input
<input #keyup="searchMovies" v-model="input" type="text" class="w-full sm:h-20 h-16 dark2 border-0 md:pl-8 pl-4 md:pr-64 pr-24 focus:outline-none text-white inline-block">
This is the error
error
And this is the results of my API request
Api results
It means that it is unable to find this. You should rewrite your function like so:
searchMovies() {
_.debounce((e) => {
axios
.get(
"https://api.themoviedb.org/3/search/movie?api_key=123456789&query=" +
e.target.value
)
.then((response) => {
this.movies.push(response.data.results);
});
}, 2000);
}
And by the way, this.movies is an object so you cannot use push.
You need to change your debounce callback to a function because an arrow function wouldn't bind this to the vue instance. In vuejs doc that writes:
Don’t use arrow functions on an options property or callback, such as
created: () => console.log(this.a) or vm.$watch('a', newValue =>
this.myMethod()). Since an arrow function doesn’t have a this, this
will be treated as any other variable and lexically looked up through
parent scopes until found, often resulting in errors such as Uncaught
TypeError: Cannot read property of undefined or Uncaught TypeError:
this.myMethod is not a function.
data () {
return {
search: false,
input: '',
movies: [],
}
},
methods:{
searchMovies: _.debounce(function(e) {
axios.get('https://api.themoviedb.org/3/search/movie?api_key=123456789&query='+e.target.value)
.then(response => {
this.movies.push(response.data.results);
})
}, 2000) }
Related
I'm trying to import a vue component based on a path entered into url parameters. So for instance, if <host>/<path> is entered into the browser, I want to import a vue component located at <path>.vue.
In my routes.js file, I have a route that will get nested paths:
{ path: 'object/:catchAll(.*)*', component: BaseObject }
And send it to BaseObject:
<template>
<div>
<component :is="componentFile" />
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
name: 'BaseObject',
data () {
return {
componentPath: '',
address: ''
}
},
methods: {
importComponent (path) {
return () => import(`./${path}.vue`)
}
},
computed: {
componentFile () {
return this.importComponent(this.componentPath)
}
},
created () {
const params = this.$route.params.catchAll
console.log(params)
this.address = params.pop()
this.componentPath = params.join('/')
}
}
</script>
When I navigate to http://localhost:8080/#/object/action, I'm expecting the component located at ./action.vue to be loaded. But this doesn't happen - instead I get the following errors:
runtime-core.esm-bundler.js?9e79:38 [Vue warn]: Invalid VNode type: undefined (undefined)
at <Anonymous>
at <BaseObject onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< null > >
at <RouterView>
at <QPageContainer>
at <QLayout view="lHh Lpr lFf" >
at <MainLayout onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< Proxy {$i18n: {…}, $t: ƒ, …} > >
at <RouterView>
at <App>
and
Uncaught (in promise) Error: Cannot find module './.vue'
at app.js:416
Does anyone know how this can be accomplished?
There are at least 2 problems with your code...
Your "catch all" route is defined as { path: 'object/:catchAll(.*)*', component: BaseObject } so if you navigate to URL http://localhost:8080/#/object/action the "object" part is matched and catchAll param will be an array containing single item "action". So the created hook will pop this single item, params array remains empty and componentPath will be empty too (this is reason for Cannot find module './.vue' error)
In Vue 3, the old async component syntax (() => import(``./${path}.vue``)) is deprecated. You should always use defineAsyncComponent helper when creating async components (and this is reason for Invalid VNode type: undefined Vue warning)
So you BaseObject should look like this:
<template>
<div>
<component :is="componentFile" />
</div>
</template>
<script>
import { defineComponent, defineAsyncComponent } from "vue";
export default defineComponent({
name: "BaseObject",
data() {
return {
componentPath: "",
address: "",
};
},
methods: {},
computed: {
componentFile() {
return defineAsyncComponent(() =>
import(`../components/${this.componentPath}.vue`)
);
},
},
created() {
const params = this.$route.params.catchAll;
console.log(this.$route.params);
// this.address = params.pop();
this.componentPath = params.join("/");
},
});
</script>
Working demo
Also note that defining "catch all" route like this is dangerous as it will match all routes as - /object/action, /object/action/dd, /object/action/dd/bb etc. and those components will not exist. So maybe it would be better to allow only one level of nesting...
I guess that you are using Webpack to bundle your application and resolve JS modules. As you can see in the docs https://webpack.js.org/api/module-methods/#import-1, import() returns a promise, it works asynchronous. You have to resolve that promise first, before using the component.
methods: {
async getComponentFile() {
const component = await this.importComponent(this.componentPath);
return component;
}
},
Don't hard easy solutions!
<component :is="componentFile" />
export default {
name: 'BaseObject',
components: {
firstComponent: () => import('...'),
secondComponent: () => import('...'),
thirdComponent: () => import('...')
}
computed: {
componentFile () {
return this.detectComponentBasedPath()
}
},
methods: {
detectComponentBasedPath () {
...
}
}
}
</script>
I have a component called External.vue which contains a button that has props and also has a Google Analytic event fired every time it is clicked.
<template>
<div>
<button
ref="ctaButtonExternalElement"
type="submit"
#click="fireEvent"
>
<a
ref="ctaButtonExternalLinkElement"
:class="ctaColour"
class="block rounded-md border border-transparent px-4 py-3 font-medium shadow focus:outline-none focus:ring-2 focus:ring-offset-2 sm:px-6"
:href="ctaLink"
>
{{ ctaTitle }}
</a>
</button>
</div>
</template>
<script>
export default {
props: {
ctaTitle: {
required: true,
type: String,
},
ctaLink: {
required: true,
type: String,
},
ctaColour: {
required: false,
type: String,
default: 'bg-blue-500 hover:bg-blue-700 focus:ring-blue-500',
},
event: {
required: false,
type: Object,
default: function() {
return {};
},
},
},
methods: {
fireEvent() {
if (this.event != null) {
return this.$ga.event({
eventCategory: this.event.event_category,
eventAction: this.event.event_action,
eventLabel: this.event.event_label,
eventValue: this.event.event_value,
});
}
},
},
};
</script>
As you can see this.$ga is injected here by nuxt automatically and in our test we are wanting to load the component so we want to inject the $ga or rather have a mocked version of it.
import {mount} from '#vue/test-utils';
import External from './External';
import ga from '#nuxtjs/google-analytics';
import {jest} from '#jest/globals';
describe('Test External Button', () => {
test('should mock google analytics', async () => {
const $ga = {ga: {
event: jest.fn(),
}};
const wrapper = mount(External, {
propsData: props,
mocks: {
$ga,
},
});
const button = wrapper.getComponent({ref: 'ctaButtonExternalElement'});
button.trigger('click');
expect($ga).toHaveBeenCalled();
});
});
when I run this test I get this error:
Test External Button › should mock google analytics
expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has type: object
Received has value: {"ga": {"event": [Function mockConstructor]}}
Error in v-on handler: "TypeError: this.$ga.event is not a function"
Is there anyway of mocking $ga?
Your mock looks incorrect. $ga has no ga property, so that should be removed from the mock.
//const $ga = {ga: {
// event: jest.fn(),
//}};
const $ga = {
event: jest.fn(),
};
And you'd verify $ga.event is called, not $ga. Also the click-event trigger should be awaited, as the click-handler isn't called until the next tick:
//button.trigger('click');
//expect($ga).toHaveBeenCalled();
await button.trigger('click');
expect($ga.event).toHaveBeenCalled();
My user state variable is an object having several properties such as first_name. I want to display some of these properties in my component template.
I'm assign my state variable to a computed property which I use in template thus:
<template>
<div>
{{ user.first_name }}
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: {
...mapState({
user: state => state.dashboard.user
})
},
beforeMount () {
this.$store.dispatch("dashboard/getUser");
}
};
</script>
Although it works, I get the following error in console:
Error in render: "TypeError: Cannot read property 'title' of null"
I suppose it's because user is null for a split second as component mounts, till it receives info that Vue correctly displays in template. How to avoid the error though?
[EDIT] here are the relevant part of the store:
state: {
user: null
},
...
actions: {
async getUser({ commit }) {
let user = await axios.get(`user`).catch(console.error);
commit("SET_USER", user);
return user;
}
},
In your mapped getter you could default to an empty object like
state => state.dashboard.user || {}
That way things like user.first_name would be undefined rather than attempting to look for a property on the value null
Ok. I've rewritten the code.
store.js
state: {
user: ''
},
mutations: {
SET_USER: (state, user) => {
state.user = user
}
},
actions: {
getUser: (context, user) => {
axios.get('url/to/server')
.then(res => {
context.commit('SET_USER', res.data)
})
.catch(error => {
console.log(error)
})
}
}
Now in your root component (App.vue for example)
import {mapActions} from 'vuex'
export default{
...
mounted() {
this.getUser()
},
methods: {
...mapActions(['getUser'])
}
}
In the component, you wish to use the user data
<template>
<div>
{{user.first_name}}
</div>
<template/>
import {mapState} from 'vuex'
export default{
computed: {
...mapState(['user'])
}
}
This will work.
Hope it helps.
I'm using Nuxt.js in static site mode, and trying to get an image from an API using a string passed in a prop, however, in the template I am getting [object Promise]. I would've thought that return before the get request would resolve the promise, but I think my grasp of promises and Nuxt.js a little off. Any help would be greatly appreciated.
<template>
<div>
{{ getThumbnailSrc() }}
</div>
</template>
<script>
import axios from 'axios'
export default {
props: {
link: {
type: String,
required: true
}
},
data() {
return {
imageUrl: null
}
},
methods: {
getVimeoId(link) {
return link.split('/').pop()
},
getThumbnailSrc() {
return axios
.get(
`https://vimeo.com/api/v2/video/${this.getVimeoId(
this.link
)}.json`
)
.then(response => {
const vimeoThumbnailUrl = response.data[0].thumbnail_large
console.log(vimeoThumbnailUrl)
return {
vimeoThumbnailUrl
}
})
.catch(error => {
console.log(error)
})
}
}
}
</script>
It sure won't! XHR requests are asynchronous and therefore the template has no idea that it needs to wait.
Solve it by using an additional data property on the component, and using that instead:
data() {
return {
imageUrl: null,
thumbnailSrc: null
}
},
And in your callback:
.then(response => {
const vimeoThumbnailUrl = response.data[0].thumbnail_large
console.log(vimeoThumbnailUrl)
this.thumbnailSrc = vimeoThumbnailUrl
})
Now you can use {{thumbnailSrc}} and it will load appropriately.
New to using Vue-Multiselect. I am using axios to do a GET request from a JSON placeholder to test.
How do I get the title and post id to show up in my drop down?
Right now, I just get [Object Object] - [title] shown in my select box.
<!-- Vue component -->
<template>
<div>
<multiselect v-model='value' :options='posts' :custom-label='postWithTitle' placeholder='Select one' label='title' track-by='id'></multiselect>
{{ value }}
</div>
</template>
<script>
import Multiselect from "vue-multiselect";
import axios from "axios";
export default {
// OR register locally
components: { Multiselect },
data() {
return {
value: null,
posts: []
};
},
created() {
this.getPosts();
},
methods: {
getPosts() {
axios
.get("https://jsonplaceholder.typicode.com/posts")
.then(response => {
// eslint-disable-next-line
console.log(response);
this.posts = response.data;
})
.catch(error => {
// eslint-disable-next-line
console.log(error);
});
},
postWithTitle(id, title) {
return `${id} - [${title}]`;
}
}
};
</script>
fix:
postWithTitle(option) {
return `${option.id} - [${option.title}]`;
}
explaination:
i saw that when i simply console.logged inside the postWithTitle function:
the custom custom-label attribute was accepting a callback that only accepts one argument. that argument was the entire option object- a single entry of your posts array.