I have a Vue (2.2.1) component that should display a membership directory by making a request to a Laravel API. The request succeeds, and the response is correct when I console.log it. Likewise, the API request succeeds in Postman and the JSON object is correctly formatted. But, the response won't pass into the variable in the Vue instance data object to which I attempt to assign it. My code:
<template>
<div>
{{ entries }}
</div>
</template>
<script>
export default {
name: 'directory',
data() {
return {
entries: []
}
},
created() {
this.getEntries()
},
methods: {
getEntries: function() {
axios.get('/api/directory')
.then(response => {
this.entries = response.data;
});
}
}
}
</script>
When I load the page (after npm run dev), empty brackets representing the initial state of entries (an empty array) is displayed and not the raw JSON data. (I'm just testing now, not yet styling or building the table). However, if I add a console.log(this.entries) or console.log(response) or console.log(response.data), the correct form of the JSON is displayed in console and there are no errors.
I have replaced the axios code with the appropriate fetch code, including the ->json() line and get the same results. I have also used a "practice" API (JSONPlaceholder) to make sure it wasn't a problem with my API and likewise got the same results. This suggests that I'm doing something wrong in my Vue component <script>, but after hours of searching SO, Google, and Vue.js forum, I'm not finding anything. The closest matching link (Vue.js cannot set data returned from external axios response) was of no help, unfortunately.
I would make your entries a computed property so it will update on the call, it seems like your template is not updating a computed property may solve this.
Try this:
data(){
return {
dataEntries: null
}
computed: {
entries(){
if (dataEntries) return dataEntries
return []
}
Related
I'm trying to create an SSG site with Nuxt.js.
When I access a route that isn't set in the generate property of nuxt.config.js,
I want to display the contents of a 404 page without changing the URL.(using htaccess)
The following is the site under construction
http://we-are-sober.d3v-svr.com/xxxx
This is working as expected.
http://we-are-sober.d3v-svr.com/user/xxxx
This does not work as expected.
The contents of page 404 are displayed for a moment, but soon after that, the process based on the dynamic route of "user/_id.vue" is executed.
The point of the problem is that the non-existent route behaves as if it exists.
Does anyone know how to solve this problem?
Here is the source code.
https://github.com/yhirochick/rewrite_test
404.vue
https://github.com/yhirochick/rewrite_test/blob/master/pages/404.vue
user/_id.vue
https://github.com/yhirochick/rewrite_test/blob/master/pages/user/_id.vue
nuxt.config.js
https://github.com/yhirochick/rewrite_test/blob/master/nuxt.config.js#L43-L45
.htaccess
https://github.com/yhirochick/rewrite_test/blob/master/static/.htaccess
I am Japanese. The above text is based on Google Translate.
It may be difficult to understand, but thank you.
My way of handling this kind of issue while minimizing the API calls required are following those steps:
generate a brand new Nuxt project
install axios: yarn add -D axios
add this to the nuxt.config.js file
import axios from 'axios'
export default {
...
generate: {
routes: async () => {
const users = await axios.get('https://jsonplaceholder.typicode.com/users')
return users.data.map((user) => ({
route: `/users/${user.id}`,
payload: user,
}))
},
fallback: 'no-user.html', // this one is not needed anymore if you ditch the redirect!
},
}
This will generate all the needed routes, while keeping the calls to a minimum thanks to payload that will be passed later on to the pages. More info can be found in the docs.
then, creating the /pages/users/_id.vue page does the trick
<template>
<div>
<div v-if="user">User name: {{ user.name }}</div>
<div v-else-if="error">{{ error }}</div>
</div>
</template>
<script>
export default {
asyncData({ payload }) {
if (payload && Object.entries(payload).length) return { user: payload }
else return { error: 'This user does not exist' } // this will also catch users going to `/users/`
},
}
</script>
create some no-user.vue page, error.vue layout and you should be gucci
At the end, we have 10 users from the mocked API. So those are the following cases:
if we go to /users/5, the user is already static so we do have it's info without any extra API call
if we go to /users/11, the user was not present at the time of build, hence he is not here and we are displaying an error
if we go to /users, we will still be sent to the /pages/users/_id page, but since the :id will be optional there, it will error and still display the error, an index.vue can of course handle this case
My github repo for this one can be found here: https://github.com/kissu/so-nuxt-generate-placeholder-users
This approach is called full static in Nuxt, as explained here: https://nuxtjs.org/announcements/going-full-static/
It's a tricky way, but I've found the code that works as I want.
https://fes.d3v-svr.com/users/xxxxxx
It's works that I expect.
User xxxxxx doesn't exist
Display 404 page
The URL /users/xxxxxx as it is
First, simply set .htaccess to rewrite non-exist page to 404 page
ErrorDocument 404 /no-user/index.html
Only above, Nuxt execute base on URL /users/xxxxxx/ and re-render the page as "UserXXXXXX" even he is not exist.
To avoid this, users/_id.vue is like bellow.
template
<template>
<div v-if="ssr">User name: {{ user.name }}</div>
</template>
script
<script>
export default {
asyncData({ payload }) {
return { user: payload, ssr:true }
},
}
</script>
It seems to be if a template is empty, nuxt will not execute the script depends on the URL.
It's very tricky, so I'll continue to how it is works.
This is a Laravel & Vue Js Project.
Everything works fine but why I'm facing [Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined" type error.
My Vue File
<template>
<p>Sold By: {{product.user.name}}</p>
</template>
<script>
export default {
data(){
return {
product:{},
}
},
methods: {
loadData(){
axios.get('/api/'+this.$route.params.slug+'/product')
.then(response => {
this.product = response.data;
},
()=> {});
}
},
created(){
this.$Progress.start();
this.loadData();
this.$Progress.finish();
},
}
</script>
My Controller
public function getProduct($slug)
{
$product = Product::where('slug',$slug)->with('brand','category','subCategory','productImages','featureDescriptions','colors','variants','user')->first();
return response()->json($product, 200);
}
``
Now I want to show my User name in Vue file <p>Sold By: {{product.user.name}}</p>. It showing User Name With an error [Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined". when I show the user data <p>Sold By: {{product.user}}</p>, It show user all data without error. Now how i show user name without error.
The error is self-explanatory: you're using {{product.user.name}} in the template. But before the product has returned from BE, product.user is undefined and therefore does not have a .name property.
The simplest fix would be to place a v-if on the <p>:
<p v-if="product.user">Sold By: {{product.user.name}}</p>
Another generic solution for this type of problem is to use a computed:
<template>
<p>Sold By: {{productUserName}}</p>
</template>
<script>
export default {
// ...
computed: {
productUserName() {
return this.product.user?.name || '';
}
}
// ...
}
</script>
You can read more about optional chaining operator (used above) (?.) here.
Because it's a fairly new addition to JavaScript, Vue doesn't currently support it in <template> tags (but it works in <script>).
Additional note: a common mistake is to add an additional data member instead of using the source of the error (product.user in this case) either directly or through a computed. This creates two problems:
it decouples product.user from rendering the <p>. Which means that if BE returns a product without a user, you'll still get the error, because you've set dataLoaded to true but the template still tries to read the property .name of user, which is falsy and therefore does not have a .name.
you create unnecessary boilerplate: anyone trying to understand or modify your code at a later time has to figure out the arbitrary connection between dataLoaded and product.user.
One of the reasons Vue is loved for is because it doesn't require boilerplate code, unlike other frameworks (i.e: Angular). Keep it that way! By using v-if="product.user" in the template, someone reading that code will immediately understand the rendering logic, without having to look at the component code. Decreasing the time needed to figure out the code on a regular basis will greatly decrease the time needed to modify it, should you (or someone else) ever need to. This results into more flexible, more scalable code. Less bugs, less time spent => more money.
This is happening because <p> is being rendered while product is still an empty object (product: {}).
You could use v-if to render only if product already has been loaded.
<template>
<p v-if="dataLoaded">Sold By: {{ product.user.name }}</p>
</template>
<script>
export default {
data() {
return {
product: {},
dataLoaded: false,
};
},
methods: {
loadData() {
axios.get("/api/" + this.$route.params.slug + "/product").then(
(response) => {
this.product = response.data;
this.dataLoaded = true;
},
() => {}
);
},
},
created() {
this.$Progress.start();
this.loadData();
this.$Progress.finish();
},
};
</script>
Using Vue.js (Vuetify for FE).
A page reload causes the getter in Vuex to fail with pulling required data from the store. The getter returns undefined. The code can be found on GitHub at: https://github.com/tineich/timmyskittys/tree/master/src
Please see the full details on this issue at timmyskittys.netlify.com/stage1. This page has complete info on the issue and instructions on how to view the issue.
Note, there is mention of www.timmyskittys.com in the issue description. This is the main site. timmyskittys.netlify.com is my test site. So, they are the same for all intents and purposes. But, my demo of this issue is at the Netlify site.
I read the complete issue in the website you mentioned. It's a generic case.
Say, for cat details page url: www.timmyskittys.com/stage2/:id.
Now in Per-Route Guard beforeEnter() you can set the cat-id in store. Then from your component call the api using the cat-id (read from getters)
I found the solution to my issue:
I had to move the call of the action which calls the mutation that loads the .json file (dbdata.json) into a computed() within App.vue. This was originally done in Stage1.vue.
Thanks all for responding.
I had the same issue and my "fix" if it can be called that was to make a timer, so to give the store time to get things right, like so:
<v-treeview
:items="items"
:load-children="setChildren"
/>
</template>
<script>
import { mapGetters } from 'vuex'
const pause = ms => new Promise(resolve => setTimeout(resolve, ms))
export default {
data () {
return {
children: []
}
},
computed: {
...mapGetters('app', ['services']),
items () {
return [{
id: 0,
name: 'Services',
children: this.children
}]
}
},
methods: {
async setChildren () {
await pause(1000)
this.children.push(...this.services)
}
}
}
</script>
Even though this is far from ideal, it works.
Both data() and async data() gives the same result (and it is obvious that the results from asyncData() override the results from data())
and both results in HTML code in the source code (i.e the code rendered in the server-side)
also, both can be used to "await" the data to be fetched (ex: using axios)
so, what is the difference between them?
<template>
<div>
<div>test: {{ test }}</div>
<div>test2: {{ test2 }}</div>
<div>test2: {{ test3 }}</div>
<div>test2: {{ test4 }}</div>
</div>
</template>
<script>
export default {
asyncData(app) {
return {
test: "asyncData",
test2: "asyncData2",
test3: "asyncData3"
};
},
data() {
return {
test: "data",
test2: "data2",
test4: "data4"
};
},
};
</script>
result:
test: asyncData
test2: asyncData2
test2: asyncData3
test2: data4
The simplest answer is data() is processed on the client side, however asyncData() section is processed on the server side on the call for Nuxt() once and on the client side once more.
The biggest advantage of nuxt is it's ability to render content on the server side. If you load your content using promise on the client side, say for example in the mounted section as:
data() {
return {
products: []
}
},
mounted() {
axios.get('/api/v1/products').then(response => {
this.products = response.data
})
}
the javascript code is sent to the client as it is and the browser is responsible to run the promise to fetch the data from the api. However if you put the promise inside asyncData:
asyncData() {
return axios.get('/api/v1/products').then(response => {
// Note that you can't access the `this` instance inside asyncData
// this.products = response.data
let products = response.data
return { products } // equivalent to { products: products }
})
}
The data fetching is done on the server side and the result is pre-rendered and an html with the data (rendered into it) is sent to the client. So in this case the client won't be receiving the javascript code to process the api call by itself, but instead it receives something like this:
<ul>
<li>
Product 1
</li>
<li>
Product 2
</li>
<li>
Product 3
</li>
</ul>
The result we return from asyncData is merged with what is in data. It's not replaced but merged.
You may want to fetch data and render it on the server-side. Nuxt.js adds an asyncData method that lets you handle async operations before setting the component data.
asyncData is called every time before loading the page component and is only available for such. It will be called server-side once (on the first request to the Nuxt app) and client-side when navigating to further routes. This method receives the context object as the first argument, you can use it to fetch some data and return the component data.
The result from asyncData will be merged with data.
export default {
data () {
return { project: 'default' }
},
asyncData (context) {
return { project: 'nuxt' }
}
}
Nuxt's main attraction is the serverside rendering part, that helps with SEO. So we can assume any deviation from the normal "Vue-way" of doing things is most likely because it is in service of the SSR (which Vue naturally doesn't allow, hence we use Nuxt). Knowing that, we can pretty much say asyncData() contains the SEO-focused data that is send on the first page-load.
Short answer => use asyncData() for fetched template-based SEO-focused content.
So I'm building an application using Laravel Spark, and therefore taking the opportunity to learn some Vue.js while I'm at it.
It's taken longer for me to get my head around it than I would have liked but I have nearly got Vue-multiselect working for a group of options, the selected options of which are retrieved via a get request and then updated.
The way in which I've got this far may well be far from the best, so bear with me, but it only seems to load the selected options ~60% of the time. To be clear - there are never any warnings/errors logged in the console, and if I check the network tab the requests to get the Tutor's instruments are always successfully returning the same result...
I've declared a global array ready:
var vm = new Vue({
data: {
tutorinstruments: []
}
});
My main component then makes the request and updates the variable:
getTutor() {
this.$http.get('/get/tutor')
.then(response => {
this.tutor = response.data;
this.updateTutor();
});
},
updateTutor() {
this.updateTutorProfileForm.profile = this.tutor.profile;
vm.tutorinstruments = this.tutor.instruments;
},
My custom multiselect from Vue-multiselect then fetches all available instruments and updates the available instruments, and those that are selected:
getInstruments() {
this.$http.get('/get/instruments')
.then(response => {
this.instruments = response.data;
this.updateInstruments();
});
},
updateInstruments() {
this.options = this.instruments;
this.selected = vm.tutorinstruments;
},
The available options are always there.
Here's a YouTube link to how it looks if you refresh the page over and over
I'm open to any suggestions and welcome some help please!
Your global array var vm = new Vue({...}) is a separate Vue instance, which lives outside your main Vue instance that handles the user interface.
This is the reason you are using both this and vm in your components. In your methods, this points to the Vue instance that handles the user interface, while vm points to your global array that you initialized outside the Vue instance.
Please check this guide page once more: https://v2.vuejs.org/v2/guide/instance.html
If you look at the lifecycle diagram that initializes all the Vue features, you will notice that it mentions Vue instance in a lot of places. These features (reactivity, data binding, etc.) are designed to operate within a Vue instance, and not across multiple instances. It may work once in a while when the timing is right, but not guaranteed to work.
To resolve this issue, you can redesign your app to have a single Vue instance to handle the user interface and also data.
Ideally I would expect your tutorinstruments to be loaded in a code that initializes your app (using mounted hook in the root component), and get stored in a Vuex state. Once you have the data in your Vuex state, it can be accessed by all the components.
Vuex ref: https://vuex.vuejs.org/en/intro.html
Hope it helps! I understand I haven't given you a direct solution to your question. Maybe we can wait for a more direct answer if you are not able to restructure your app into a single Vue instance.
What Mani wrote is 100% correct, the reason I'm going to chime in is because I just got done building a very large scale project with PHP and Vue and I feel like I'm in a good position to give you some advice / things I learned in the process of building out a PHP (server side) website but adding in Vue (client side) to the mix for the front end templating.
This may be a bit larger than the scope of your multiselect question, but I'll give you a solid start on that as well.
First you need to decide which one of them is going to be doing the routing (when users come to a page who is handling the traffic) in your web app because that will determine the way you want to go about using Vue. Let's say for the sake of discussion you decide to authenticate (if you have logins) with PHP but your going to handle the routing with Vue on the front end. In this instance your going to want to for sure have one main Vue instance and more or less set up something similar to this example from Vue Router pretending that the HTML file is your PHP index.php in the web root, this should end up being the only .php file you need as far as templating goes and I had it handle all of the header meta and footer copyright stuff, in the body you basically just want one div with the ID app.
Then you just use the vue router and the routes to load in your vue components (one for each page or category of page works easily) for all your pages. Bonus points if you look up and figure using a dynamic component in your main app.vue to lazy load in the page component based on the route so your bundle stays small.
*hint you also need a polyfill with babel to do this
template
<Component :is="dynamicComponent"/>
script
components: {
Account: () => import('./Account/Account.vue'),
FourOhFour: () => import('../FourOhFour.vue')
},
computed: {
dynamicComponent() {
return this.$route.name;
}
},
Now that we are here we can deal with your multiselect issue (this also basically will help you to understand an easy way to load any component for Vue you find online into your site). In one of your page components you load when someone visits a route lets say /tutor (also I went and passed my authentication information from PHP into my routes by localizing it then using props, meta fields, and router guards, its all in that documention so I'll leave that to you if you want to explore) on tutor.vue we will call that your page component is where you want to call in multiselect. Also at this point we are still connected to our main Vue instance so if you want to reference it or your router from tutor.vue you can just use the Vue API for almost anything subbing out Vue or vm for this. But the neat thing is in your main JS file / modules you add to it outside Vue you can still use the API to reference your main Vue instance with Vue after you have loaded the main instance and do whatever you want just like you were inside a component more or less.
This is the way I would handle adding in external components from this point, wrapping them in another component you control and making them a child of your page component. Here is a very simple example with multiselect pretend the parent is tutor.vue.
Also I have a global event bus running, thought you might like the idea
https://alligator.io/vuejs/global-event-bus/
tutor.vue
<template>
<div
id="user-profile"
class="account-content container m-top m-bottom"
>
<select-input
:saved-value="musicPreviouslySelected"
:options="musicTypeOptions"
:placeholder="'Choose an your music thing...'"
#selected="musicThingChanged($event)"
/>
</div>
</template>
<script>
import SelectInput from './SelectInput';
import EventBus from './lib/eventBus';
export default {
components: {
SelectInput
},
data() {
return {
profileLoading: true,
isFullPage: false,
isModalActive: false,
slackId: null,
isActive: false,
isAdmin: false,
rep: {
id: null,
status: '',
started: '',
email: '',
first_name: '',
},
musicTypeOptions: []
};
},
created() {
if (org.admin) {
this.isAdmin = true;
}
this.rep.id = parseInt(this.$route.params.id);
this.fetchData();
},
mounted() {
EventBus.$on('profile-changed', () => {
// Do something because something happened somewhere else client side.
});
},
methods: {
fetchData() {
// use axios or whatever to fetch some data from the server and PHP to
// load into the page component so say we are getting the musicTypeOptions
// which will be in our selectbox.
},
musicThingChanged(event) {
// We have our new selection "event" from multiselect so do something
}
}
};
</script>
this is our child Multiselect wrapper SelectInput.vue
<template>
<multiselect
v-model="value"
:options="options"
:placeholder="placeholder"
label="label"
track-by="value"
#input="inputChanged" />
</template>
<script>
import Multiselect from 'vue-multiselect';
export default {
components: { Multiselect },
props: {
options: {
type: [Array],
default() {
return [];
}
},
savedValue: {
type: [Array],
default() {
return [];
}
},
placeholder: {
type: [String],
default: 'Select Option...'
}
},
data() {
return {
value: null
};
},
mounted() {
this.value = this.savedValue;
},
methods: {
inputChanged(selected) {
this.$emit('selected', selected.value);
}
}
};
</script>
<style scoped>
#import '../../../../../node_modules/vue-multiselect/dist/vue-multiselect.min.css';
</style>
Now you can insure you are manging the lifecycle of your page and what data you have when, you can wait until you get musicTypeOptions before it will be passed to SelectInput component which will in turn set up Multiselect or any other component and then handle passing the data back via this.$emit('hihiwhatever') which gets picked up by #hihiwhatever on the component in the template which calls back to a function and now you are on your way to do whatever with the new selection and pass different data to SelectInput and MultiSelect will stay in sync always.
Now for my last advice, from experience. Resist the temptation because you read about it 650 times a day and it seems like the right thing to do and use Vuex in a setup like this. You have PHP and a database already, use it just like Vuex would be used if you were making is in Node.js, which you are not you have a perfectly awesome PHP server side storage, trying to manage data in Vuex on the front end, while also having data managed by PHP and database server side is going to end in disaster as soon as you start having multiple users logged in messing with the Vuex data, which came from PHP server side you will not be able to keep a single point of truth. If you don't have a server side DB yes Vuex it up, but save yourself a headache and wait to try it until you are using Node.js 100%.
If you want to manage some data client side longer than the lifecycle of a page view use something like https://github.com/gruns/ImmortalDB it has served me very well.
Sorry this turned into a blog post haha, but I hope it helps someone save themselves a few weeks.