How to make an intersection between 2 datasets in Nuxt? - javascript

I tried to get data from api with params which come from an argument in a v-for.
In findUser method, I can console.log the data I'm looking for. But I can't get it at the end of findUser, why?
I know there is an async method to get it but I don't understand how to manage it to make it work with what I want to do;
I also thought about calling the two API at the same time, but the result is the same, I don't know how to manage it.
<template>
<div>
<h4>Listes Reçues</h4>
<p v-for="element in results" id="flex-list" :key="element.list_id">
{{ element.list_name }} de {{ findUser(element.user_id) }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
results: '',
nickname: '',
}
},
created() {
this.$axios
.get(`/api/listReceived/${this.$auth.user[0].user_id}`)
.then((res) => {
this.results = res.data
console.log(JSON.stringify(this.results))
})
.catch((err) => {
console.error(err)
})
},
methods: {
findUser(id) {
console.log(id)
let data = ''
this.$axios
.get(`http://localhost:3000/api/userdata/${id}`)
.then((res) => {
data = res.data[0].nickname
console.log(data)
})
.catch((err) => {
console.error(err)
})
return data
},
},
}
</script>

On top of my top answer which was quite not on point regarding the question but still relevant, here is an example on how to handle an intersection properly.
I did not used an endpoint but mocked the data locally in data() hence why I keep my post above.
<template>
<div class="flex flex-col items-center">
<h1 class="p-4 bg-green-700 rounded-md">
List of users ordered by their according message
</h1>
<!-- <pre>{{ messages }}</pre> -->
<section>
<div v-for="user in groupedMessages" :key="user.id" class="mt-4">
<p>
User: <b>{{ user.name }}</b>
</p>
<aside>
Messages:
<span v-if="!user.messages.length">No messages actually</span>
</aside>
<p v-for="message in user.messages" :key="message.id">
<span class="italic">- {{ message.text }}</span>
</p>
</div>
</section>
</div>
</template>
<script>
// ES version of lodash, lighter overall
import { cloneDeep } from 'lodash-es'
export default {
name: 'Index',
data() {
return {
messages: [
{
id: 1,
text: 'Hello world',
userId: 1,
},
{
id: 2,
text: 'Nice cool message',
userId: 1,
},
{
id: 3,
text: 'Still for the first user?',
userId: 1,
},
{
id: 4,
text: 'Yep, apparently...',
userId: 1,
},
{
id: 5,
text: "Eh, surprise, I'm a sneaky one...",
userId: 3,
},
{
id: 6,
text: 'Oh, a second one.',
userId: 2,
},
{
id: 7,
text: "You're damn right!!",
userId: 2,
},
],
users: [
{
name: 'Patrick',
id: 1,
messages: [],
},
{
name: 'Pablo',
id: 2,
messages: [],
},
{
name: 'Unkown author',
id: 5,
messages: [],
},
{
name: 'Escobar',
id: 3,
messages: [],
},
],
}
},
computed: {
groupedMessages() {
// we use that to avoid any kind of further mutation to the initial `users` array
const clonedUsers = cloneDeep(this.users)
// we do loop on each message and find a corresponding user for it
this.messages.forEach((message) =>
clonedUsers.forEach((user) => {
if (user.id === message.userId) {
user.messages.push(message)
}
})
)
return clonedUsers
},
},
}
</script>
The github repo is available, please do not pay attention to the unrelated name of it.
This is how it looks on Netlify.

created() is totally fine with Vue but usually you do use fetch() and asyncData() hooks in Nuxt.
Here is the basic idea using JSONplaceholder's API.
Here is a possible /pages/index.vue
<template>
<div class="flex flex-col items-center">
<h1 class="p-4 bg-green-700 rounded-md">
List of users from JSONplaceholder
</h1>
<section class="mt-4">
<p v-for="user in users" :key="user.id">
{{ user.name }} 🚀 {{ user.email }} ~
<nuxt-link
:to="{ name: 'users-id', params: { id: user.id } }"
class="border-b-4 border-green-500"
>
Check details
</nuxt-link>
</p>
</section>
</div>
</template>
<script>
export default {
name: 'Index',
data() {
return {
users: [],
}
},
async fetch() {
this.users = await this.$axios.$get('/users')
},
}
</script>
<style>
* {
#apply bg-gray-800 text-gray-100;
}
</style>
And the detailed page aka /pages/users/_id.vue using the fetch() hook.
<template>
<div class="flex flex-col items-center">
<nuxt-link class="p-4 bg-purple-700 rounded-md" :to="{ name: 'index' }">
Go back
</nuxt-link>
<h2>User details ID: # {{ $route.params.id }}</h2>
<p v-if="$fetchState.pending">Loading user's info...</p>
<p v-else-if="$fetchState.error">Error while fetching user</p>
<div v-else>
<p>{{ user.name }}</p>
<p>{{ user.phone }}</p>
<p>{{ user.website }}</p>
<p>{{ user.company.name }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'UserId',
data() {
return {
user: {},
}
},
async fetch() {
this.user = await this.$axios.$get(`/users/${this.$route.params.id}`)
},
}
</script>
I do prefer this approach because it's not blocking the render, you can add some smooth skeleton to still let the user know that something is happening. On top of that, fetch() is available on both components and pages while asyncData() is only for pages.
It also gives the nice $fetchState helper that can also be quite handy!
Here is the same /pages/users/_id.vue page using the asyncData() hook.
<template>
<div class="flex flex-col items-center">
<nuxt-link class="p-4 bg-purple-700 rounded-md" :to="{ name: 'index' }">
Go back
</nuxt-link>
<p>{{ user.name }}</p>
<p>{{ user.phone }}</p>
<p>{{ user.website }}</p>
<p>{{ user.company.name }}</p>
</div>
</template>
<script>
export default {
name: 'UserId',
async asyncData({ route, $axios }) {
const user = await $axios.$get(`/users/${route.params.id}`)
return { user }
},
}
</script>
Main benefit of using asyncData is the fact that it's more safe and that it's blocking the render (can be either a pro or a con, more of a con for me personally).
Here are some other in-depth answers comparing fetch() vs asyncData().
Check out this handy blog article on the subject and also this dev.to clone example.
Finally, if you want to take the SSG path and optimize the whole thing with the least amount of API calls once on the client-side, you can also check my other answer.

Related

Having problem in fetching data from api using nuxtjs with async fetch() method

I am having problem while fetching data from api in nuxtjs while using async fetch() method. Error is Cannot read properties of undefined (reading '$get'). I tried many times but still it is showing the problem.
index.vue
<template>
<div class="container">
<h1>Blog posts</h1>
<p v-if="$fetchState.pending">Fetching posts...</p>
<p v-else-if="$fetchState.error">Error while fetching posts: {{ $fetchState.error.message }}</p>
<ul v-else>
<li v-for="post of posts" :key="post.id">
<n-link :to="`/posts/${post.id}`">{{ post.title }}</n-link>
</li>
</ul>
<nuxt-link to="about">about</nuxt-link>
</div>
</template>
<script>
export default {
data() {
return {
posts: [],
};
},
head() {
return {
title: "About the app",
meta: [
{
hid: "Description",
name: "Description",
content: "hello",
},
],
};
},
async fetch() {
posts = await this.$http.$get("https://jsonplaceholder.typicode.com/posts");
console.log(posts);
},
fetchOnServer: false,
};
</script>

Can get variable from secondary table in api on a list page, but not on single item page

This page reading and displaying a full table from an API works perfectly;
<template>
<b-col>
<h2>
Enrolments
<b-button :to="{ name: 'createEnrolment', params: { id: this.$route.params}}" variant="warning" class="float-right">Create</b-button>
</h2>
<b-card
v-for="enrolment in enrolments"
:key="enrolment._id"
>
<p>Course id:{{ enrolment.course.id }}</p>
<p>Course title:{{ enrolment.course.title }}</p>
<p>Status:{{ enrolment.status }}</p>
<p>Created:{{ enrolment.created_at }}</p>
<p>Date:{{ enrolment.date }}</p>
<p>Lecturer:{{ enrolment.lecturer.name }}</p>
<p>Lecturer email:{{ enrolment.lecturer.email }}</p>
<p>Updated:{{ enrolment.updated_at }}</p>
<p>Time:{{ enrolment.time }}</p>
<b-button :to="{ name: 'viewEnrolment', params: { id: enrolment.id}}" variant="warning">View</b-button>
</b-card>
</b-col>
</template>
<script>
import axios from '#/config'
export default {
name: "viewEnrolments",
components: {},
data(){
return {
enrolments: []
}
},
mounted() {
this.getData()
},
methods: {
getData() {
let token = localStorage.getItem('token')
axios
.get(`/enrolments`,
{
headers: {
"Accepted": `application/json`,
"Authorization": `Bearer ${token}`
}
})
.then(response => {
console.log(response.data)
this.enrolments = response.data.data
})
.catch(error => console.log(error))
}
}
}
</script>
However, when I try to view just one entry to the enrolments table, it cannot recognise or get that data from the courses table, giving the error:
"TypeError: Cannot read properties of undefined (reading 'id')", which comes from line 8: <p>Course id:{{ enrolment.course.id }}</p>
<template>
<b-col>
<h2>
Enrolments
</h2>
<b-card>
<p>Course id:{{ enrolment.course.id }}</p>
<p>Course title:{{ enrolment.course.title }}</p>
<p>Status:{{ enrolment.status }}</p>
<p>Created:{{ enrolment.created_at }}</p>
<p>Date:{{ enrolment.date }}</p>
<p>Lecturer:{{ enrolment.lecturer.name }}</p>
<p>Lecturer email:{{ enrolment.lecturer.email }}</p>
<p>Updated:{{ enrolment.updated_at }}</p>
<p>Time:{{ enrolment.time }}</p>
</b-card>
</b-col>
</template>
<script>
import axios from '#/config'
export default {
name: "viewEnrolment",
components: {},
data(){
return {
enrolment: []
}
},
mounted() {
this.getData()
},
methods: {
getData() {
let token = localStorage.getItem('token')
axios
.get(`/enrolments/${this.$route.params.id}`,
{
headers: {
"Accepted": `application/json`,
"Authorization": `Bearer ${token}`
}
})
.then(response => {
console.log(response.data)
this.enrolments = response.data.data
})
.catch(error => console.log(error))
},
}
}
</script>
I tried a few different ways to link the courses table to the enrolment one, but nothing worked. But I don't even understand what I have in the first one that allows me to reference the courses table, but not in the second.
I think you might have misspelled your variable in the second component, viewEnrolment. You have the plural enrollments:
this.enrolments = response.data.data
But in your component HTML, you're using the singular enrollment:
<p>Course id:{{ enrolment.course.id }}</p>
Also, this is just an additional tidbit: In Vue components, I would typically try to call API's in the created() hook, not the mounted() hook. created() comes before, and is called when data observation is set up, but before the component is actually mounted to HTML.
This could prevent weird issues where when trying to use this.enrolment in your HTML, it's not actually being available yet, because the API request isn't finished.

Access nested nodes with GraphQL (Nuxt)

i’m experiencing some issues with Apollo, GraphQL and Nuxt. i don’t know if it’s especially related to Nuxt though, or with vue.
i’m trying to use WordPress as headless CMS via WP-GraphQL plugin. here’s my query
WP-GraphQL interface
i basically created a graphql folder with a posts.js file inside, that contains my query
import gql from 'graphql-tag'
export const myQuery = gql`
query myQuery {
posts {
nodes {
id
title
date
slug
author {
node {
name
}
}
featuredImage {
node {
uri
sourceUrl
srcSet
}
}
}
}
}
`
then, all i need to do is to print my data in the template. here's the script part first.
<script>
import { myQuery } from '~/graphql/posts'
export default {
data() {
return {
posts: [],
}
},
apollo: {
posts: {
prefetch: true,
query: myQuery,
},
},
watch: {
async $route() {
await this.$nuxt.refresh()
window.scrollTo(0, 0)
},
},
transition: 'home',
async mounted() {
this.posts = await this.$apollo.query({ query: myQuery })
this.posts = this.posts.data.posts.nodes
this.loading = false
}
</script>
and then comes the template :
<template>
<section class="featured-projects">
<div class="featured-projects__wrapper">
<article v-for="post in posts" :key="post.id">
<p>{{ post.id }}</p>
<h2>{{ post.title }}</h2>
<span>{{ post.date }}</span>
</article>
</div>
</section>
</section>
</template>
everything just works!
now, i would like to print post author name as well. i first tried this :
<span>{{ post.author }}</span>
and this actually prints this :
{
"node": {
"name": "max max",
"__typename": "User"
},
"__typename": "NodeWithAuthorToUserConnectionEdge"
}
it totally makes sense, as author is an object with nested items in it. so according to what i’m being returned and following GraphQL API structure, to display post author name, think i should do something like this instead :
<span>{{ post.author.node.name }}</span>
and here’s the error i get, and i don’t know what to do to access what i want :
Cannot read property 'node' of undefined.
your problem arises from reading the data before it is loaded.
depending on your js settings you should be able to use one of the following:
<span>{{ post?.author.node.name }}</span>
or <span>{{ post ? post.author.node.name : '' }}</span>
according to the Vue Apollo documentation it could also be a problem with the duplication of the query
<script>
import { myQuery } from '~/graphql/posts'
export default {
data() {
return {
posts: [], // initialization
}
},
apollo: {
posts: {
prefetch: false, // to prevent SSR
query: myQuery,
update: data => {
console.log('overwrite posts with new data', data.posts)
return data.posts
}
},
}
}
</script>
further as there seem to be cases in which the author has more than one entry (perhaps co authors?) I would try to update the author rendering to the following:
<template>
<section class="featured-projects">
<div class="featured-projects__wrapper">
<article v-for="post in posts" :key="post.id">
<p>{{ post.id }}</p>
<div v-if="Array.isArray(post.author)">
first author is: {{ post.author[0].node.name }}
</div>
<div v-else-if="post.author">
author is: {{ post.author.node.name }}
</div>
<div v-else="post.author">
no author
</div>
</article>
</div>
</section>
</section>
</template>

I can't display properly v-data-table data: ''Invalid prop: type check failed for prop "items". Expected Array, got Object''

I'm starting a project in which I had to use Vue. I'm actually really new to this, so I'm learning on the go. I do apologize in advance since this question have answered before, however, I didn't really understand the solutions provided, which is why I'm here asking myself.
Well, I was trying to display some data on my Data Table (more specifically, v-data-table from Vuetify). I was able to get the data from the API, but, for some reason it doesn't show me anything. Thanks to Vuex I can see that the mutation worked because on the console on Google Chrome I can see the Array of objects. But as I said, it still does't show me a single thing on the table, it even says 'no data available'. Some errors that I get are things like '[Vue warn]: Invalid prop: type check failed for prop "items". Expected Array, got Object' and 'TypeError: this.items.slice is not a function'.
Here is the code from List.vue
<template>
<v-container id="data-tables" tag="section">
<div class="text-right">
<v-btn class="mx-2" fab dark color="primary" :to="{ name: 'UserCreate' }">
<v-icon dark>mdi-plus</v-icon>
</v-btn>
</div>
<base-material-card
color="indigo"
icon="mdi-vuetify"
inline
class="px-5 py-3"
>
<template v-slot:after-heading>
<div class="display-2 font-weight-light">
Lista de Empleados
</div>
</template>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
class="ml-auto"
label="Search"
hide-details
single-line
style="max-width: 250px;"
/>
<v-divider class="mt-3" />
<v-data-table
:headers="headers"
:items="users"
:search.sync="search"
:sort-by="['name', 'office']"
:sort-desc="[false, true]"
multi-sort
>
<template v-slot:item.actions="{ item }">
<v-icon small class="mr-2" #click="editItem(item)">
mdi-eye
</v-icon>
<v-icon
small
class="mr-2"
#click="editItem(item)"
:to="{ name: 'UserUpdate' }"
>
mdi-pencil
</v-icon>
<v-icon small #click="deleteItem(item)">
mdi-delete
</v-icon>
</template>
</v-data-table>
</base-material-card>
</v-container>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'UsersTable',
data() {
return {
headers: [
{
text: 'Nombre',
value: 'empleado.nombre',
},
{
text: 'Apellido',
value: 'empleado.apellido',
},
{
text: 'Dirección',
value: 'empleado.direccion',
},
{
text: 'Correo Electrónico',
value: 'email',
},
{
text: 'Teléfono',
value: 'empleado.telefono',
},
{
sortable: false,
text: 'Actions',
value: 'actions',
},
],
loader: true,
search: undefined,
}
},
created() {
this.$store.dispatch('users/fetchUsers')
},
computed: {
...mapState(['users']),
},
methods: {},
mounted() {},
}
</script>
And the code from user.js, where the fetchUsers it's coming from.
import auth from '#/api/auth'
export const namespaced = true
export const state = {
users: [],
}
export const mutations = {
SET_USERS(state, users) {
state.users = users
},
}
export const actions = {
fetchUsers({ commit, dispatch }) {
auth
.getAllAccounts()
.then((response) => {
commit('SET_USERS', response.data)
})
.catch((error) => {
const notification = {
type: 'error',
message: 'There was a problem fetching users: ' + error.message,
}
dispatch('notification/add', notification, { root: true })
})
},
}
Thanks in advance.
You are not getting the correct user from vuex, because is namespaced, change to:
computed: {
...mapState('users',['users']),
},
MapState helper dosen't work the same way like the other helpers because the state module isn't registred in the global namespace. So namespacing your module will help or you do it in this way:
computed: {
...mapState({
users: state => state.FilenameOfYourModule.users
})
}

How create multiple store filter in VueJS?

i'm new to VueJS and this is my first big project. In this project I have 2 filters for my elements, one is a search bar and the other is based on checkboxs. As you can see in my code, I have a computed propertie with 2 filters, the second filter is supposed to display the products with the same brand bu it doesn't work and I don't know why. If anybody have an idea it would be cool ;)
<div class="col-xs-12">
<input type="text" v-model="search" placeholder="Rechercher" class="search">
</div>
<div class="row">
+ filtres
<transition name="fade">
<div v-if="show" class="filter__container">
<ul>
<li>
<input type="checkbox" v-model="getBrand" v-on:click="filtered" v-bind:value="brand" />
<label for="apple">Apple</label>
</li>
<li>
<input type="checkbox" v-model="getBrand" v-on:click="filtered" v-bind:value="brand" />
<label for="samsung">Samsung</label>
</li>
</ul>
</div>
</transition>
</div>
<div class="row between-xs no-margin grid">
<div v-for="product in filtered" class="containers no-padding no-margin item">
<router-link to="/items">
<div #click="getProduct(product)">
<img :src="product.img" :alt="product.alt" class="img">
<div class="content">
<h3>{{ product.title }}</h3>
<p>{{ product.description }}</p>
<p>{{ product.brand }}</p>
</div>
</div>
</router-link>
</div>
</div>
<script>
import app from '../App'
import {mapActions} from 'vuex';
export default {
components: {
app
},
data() {
return {
show: false,
search: '',
brand: ['apple','samsung'],
getBrand:[],
}
},
computed: {
products() {
return this.$store.state.products;
},
filtered: function () {
return this.products.filter((product) => {
return product.title.toLowerCase().match(this.search.toLowerCase())
return product.brand.match(this.getBrand.includes(brand))
})
},
},
methods: {
...mapActions([
'currentProduct',
]),
getProduct(product) {
this.currentProduct(product);
}
},
};
</script>
export const store = new Vuex.Store({
state: {
products: [{
img: '../icons/img.png',
alt: 'logo',
title: 'Title',
description: 'Description',
brand: 'Apple'
},
{
img: '../icons/img.png',
alt: 'logo',
title: 'Title2',
description: 'Description2',
brand: 'Apple'
},
{
img: '../icons/img.png',
alt: 'logo',
title: 'Title3',
description: 'Description3'
brand: 'Samsung'
},
{
img: '../icons/img.png',
alt: 'logo',
title: 'Title4',
description: 'Description4'
brand: 'Samsung'
}
],
currentProduct: {},
},
getters: {
getProduct: state => state.currentProduct,
},
mutations: {
CURRENT_PRODUCT: (state, product) => {
state.currentProduct = product;
}
},
actions: {
currentProduct: (context, product) => {
context.commit('CURRENT_PRODUCT', product);
}
}
})
You can't return twice from the same function. Either chain the conditions using && or chain in another call to filter.
You're also misusing match. The argument needs to be a RegExp or something that can safely be converted to a RegExp. You can see the problem in the console if you type in a character like [ that has a special meaning in a RegExp. Perhaps you meant includes?
The second condition also seems to be incorrect. Not entirely clear what that combination of match and includes is trying to achieve but I think you're looking for something like this:
return this.products.filter((product) => {
return product.title.toLowerCase().includes(this.search.toLowerCase()) &&
this.getBrand.includes(product.brand)
})
It is worth noting that while both conditions are using a method called includes they are two different methods, one on a string and the other on an array.
This also seems to be wrong:
v-bind:value="brand"
brand is an array of strings and you aren't looping over them with a v-for. Change it to value="Apple" and value="Samsung" instead, ensuring the case matches the data.
I also suggest removing v-on:click="filtered". Not sure what that's trying to do but it seems to be treating a computed property as a click listener.

Categories

Resources