Rendering data in Vue - vue.js

I'm trying to create a Card component in Vue that renders some data I pull from firebase. In React I'd pretty much just use map and render out a <Card /> component then go create the Card component separately. Something like this
anArray.map((item, index) => return <Card key={index} index={index} item={item}/>
That would give me the specific item and I could work off the data from there. I'm not sure how to accomplish the same type of thing in Vue. I've created a JSFiddle. The data and bootstrap is already pulled in. I store the data in an object. Is storing it in an object or an array preferred?
https://jsfiddle.net/agzLz554/

The data should be an array because you actually need render item list. If you store it in object, you need to extract key/value mapping, and flatten to array. Because you question actually has nothing to do with the parse, so i set the data directly into vue data.
There are 4 problems:
you should store data in array, not object
you should not set template attribute in vue instance, it's used for component, not the root instance
your card html code should live inside in #app, otherwise, it can't be recognize
your card code loop, should not set id='card', id should be unique
check this JSFiddle example, https://jsfiddle.net/seaify/najm1uer/
<script src="https://unpkg.com/vue#2.0.3/dist/vue.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.4.1/firebase.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css" integrity="sha384-AysaV+vQoT3kOAXZkl02PThvDr8HYKPZhNT5h/CXfBThSRXQ6jW5DO2ekP5ViFdi" crossorigin="anonymous">
<div id="app">
<div class="card" v-for="resource in resources">
<div class="card-header">
<!-- Author Name -->
</div>
<div class="card-block">
<h4 class="card-title">{{resource.title]}}<!-- Article Title --></h4>
<p class="card-text">{{resource.desc}}<!-- Article Description --></p>
Go somewhere
</div>
</div>
</div>
/**
* Created by chuck on 2/11/16.
*/
var vm = new Vue({
el: '#app',
data () {
return {
resources: [
{
"objectId": "-KVSES7sKx-kk31gPyiz",
"authorId": "3F14Sh3vCMXhRfZyxRPBPzn8Rdf2",
"authorImage": "https://lh3.googleusercontent.com/-5QsKgD8Li8k/AAAAAAAAAAI/AAAAAAAABbY/v-uLFhw6k8Q/photo.jpg",
"authorName": "Maxwell Gover",
"desc": "Other developers actually have to use the APIs you design. So don’t let those APIs suck.",
"quiz": [
{
"options": [
{
"isAnswer": true,
"text": "True"
},
{
"isAnswer": false,
"text": "False"
}
],
"text": "The more dependencies you have, the more potential problems it can cause in downstream consumer code"
},
{
"options": [
{
"isAnswer": true,
"text": "True"
},
{
"isAnswer": false,
"text": "False"
}
],
"text": "You should try to keep your code as self contained as possible."
}
],
"timesPassed": 0,
"title": "How to design APIs that don't suck",
"type": "article",
"url": "https://medium.freecodecamp.com/https-medium-com-anupcowkur-how-to-design-apis-that-dont-suck-922d864365c9#.my0qpwrp5"
},
{
"objectId": "-KVSXfEragBTcqcyMTAR",
"authorId": "3F14Sh3vCMXhRfZyxRPBPzn8Rdf2",
"authorImage": "https://lh3.googleusercontent.com/-5QsKgD8Li8k/AAAAAAAAAAI/AAAAAAAABbY/v-uLFhw6k8Q/photo.jpg",
"authorName": "Maxwell Gover",
"desc": "How to prioritize improving user experience",
"quiz": [
{
"options": [
{
"isAnswer": true,
"text": "True "
},
{
"isAnswer": false,
"text": "False"
}
],
"text": "No matter how clever a design is, it is rendered useless if it fails to be understood."
}
],
"timesPassed": 0,
"title": "7 Tips to Improve Your UX Strategy",
"type": "article",
"url": "https://uxdesign.cc/7-tips-to-improve-your-ux-strategy-5e39f5ff0a41#.n103o5inc"
}
]}
}
})
If you need to write code like <Card key={index} index={index} item={item}/>, all you need to do is just extract your card html code to a component's template.
Vue.component('card', {
template: '<div>A custom component!</div>'
})

In addition to seaify's answer, if you register card as a component and iterate via v-for
Vue.component('card', {...})
<card v-for="resource in resources">
...
</card>
then you have to take care of that you may have some performance issues because of the reactivity system. In Vue.js, initializing a component is a little bit expensive.
If you really need to iterate a component, you should use a functional component: https://vuejs.org/guide/render-function.html#Functional-Components
Iterating a normal component is about 6~10x slower than functional component on my computer. Here is a simple performance test, iterate a component 2000 times (about 400 ~ 600 ms):
(open dev console to see the result)
http://codepen.io/CodinCat/pen/yaGAWd?editors=1010
vs functional component (about 40 ~ 60 ms):
http://codepen.io/CodinCat/pen/XjowAm?editors=1010

Related

Is it OK to modify props if they come from a Pinia store?

I usually use Vue2 to just quickly knock up a single file app. I am now using vue3, mulitiple components and pinia.
It would really help for someone to explain the basics of components to me, with regard to updating a store.
My limited experience of using components is that you never update the props. That said, I have an app (bare bones below) where I am using v-model successfully on the props.
I have a Form, and have created the following state for it:
import { defineStore } from "pinia";
export default defineStore("form", {
state: () => ({
screens: [
{
fields: [
{ type: "text", name: "name" },
{ type: "email", name: "email" },
],
},
{
fields: [
{ type: "number", name: "age" },
{ type: "date", name: "start_date" },
],
},
],
}),
});
Now in my form component, I plan on having something like this:
<template>
<h1>Create a form</h1>
<div class="stack form">
<AdminScreen
v-for="(screen, s) in form.screens"
:key="`screen-${s}`"
class="admin-screen"
>
<AdminField
v-for="(field, f) in screen.fields"
:key="`field-${f}-${s}`"
:field="field"
/>
</AdminScreen>
</div>
</template>
<script setup>
import useFormStore from "../../useFormStore.js";
import AdminField from "../../components/AdminField.vue";
import AdminScreen from "../../components/AdminScreen.vue";
const form = useFormStore();
</script>
This renders nicely, and in my AdminField I use v-model:
<label :for="`label-${id}`">Label</label>
<input
:id="`label-${id}`"
type="text"
v-model="field.label"
#change="setKey()"
/>
In my #change function I'd like to create a key if one doesn't exist, so now I am doing things like this:
const setKey = () => {
if (!props.field.key) {
props.field.key = lettersAndNumbersOnly(props.field.label)
.toLowerCase()
.trim()
.replaceAll(" ", "_");
}
};
So here I am directly modifying the props, not even through v-model. This all works great, I'm just a bit worried after reading things like this:
https://vuejs.org/guide/components/props.html#one-way-data-flow
Can anyone advise if what I'm doing OK, or if not, the best way to refactor what I have? I looked into mutations, but I couldn't work out if I should be passing a unique id or something to the AdminField, then mutating the store from there, or if I should be $emitting like I used to in Vue2, then adding some very complex listener to the component node like <AdminField #updated="$store.commit({screenIndex: s, fieldIndex: f})" /> to then update the store.
I have also asked this on the Pinia github discussions but with no input.

How to make BigCommerce Category Description accessible to display within navigation

I am building a custom navigation for a BigCommerce site and need to display category descriptions within the navigation items in addition to name and url.
My navigation is being displayed via a component so I know I can't request Front Matter there, but as name and url are already accessible I thought the description would be as well..
Here is part of my navigation code where i'm trying to display the data. Any tips on how to achieve this would be appreciated!
{{#each children}}
<li class="navigation__submenu-item">
<a class="navigation__submenu-action has-subMenu"
href="{{url}}"
>
{{#if image}}
<img class="navigation__submenu-img" src="{{getImage image}}" alt="{{image.alt}}" />
{{/if}}
<div>{{name}}</div>
<small class="navigation__submenu-msg">{{description}}</small>
</a>
</li>
{{/each}}
You do indeed need to specify to show the description with Front Matter. Something that is not explained well in the BC documentation is that you can actually specify global Front Matter in the config.json file. Assuming you are developing local with the Stencil CLI, you can edit the config.json file. You will look for the "resources" object and append the following code to it:
"categories": {
"description": true
}
So, if your resources object looks like this (default Cornerstone):
"resources": {
"cart": false,
"bulk_discount_rates": false,
"shop_by_brand": {
"limit": 10
}
},
Change it to this:
"resources": {
"cart": false,
"bulk_discount_rates": false,
"shop_by_brand": {
"limit": 10
},
"categories": {
"description": true
}
},

Best way to organize communication between Vue.js components in this case?

I have an array
[
{
"id": 0,
"title": "Task #1",
"description": "Task description",
"solutions": [
{
"solutionId": 0,
"text": "First solution text",
"isMarked": true
},
{
"solutionId": 1,
"text": "Second solution text",
"isMarked": false
}
]
},
{
"id": 1,
"title": "Task #2",
"description": "Task description",
"solutions": [
{
"solutionId": 0,
"text": "First solution text",
"isMarked": true
},
{
"solutionId": 1,
"text": "Second solution text",
"isMarked": true
},
{
"solutionId": 2,
"text": "Third solution text",
"isMarked": true
}
]
}
]
It represents a list of tasks and solution variants.
I need an opportunity to toggle value isMarked of solution to true/false just with click on it.
I created a components structure like this:
// index.vue
<template>
<div class="task-list">
<Task v-for="task in tasks" :key="task.id" :task="task" />
</div>
</template>
<script>
// ...
data() {
return {
tasks: [/* the array of tasks */]
}
}
</script>
// task.vue
<template>
<div>
<h1>{{ task.title }}</h1>
<p>{{ task.description }}</p>
<SolutionsList :solutions="task.solutions" />
</div>
</template>
// ...
// solutionsList.vue
<template>
<div>
<Solution v-for="solution in solutions" :key="solution.id" :solution="solution" />
</div>
</template>
// ...
// solution.vue
<template>
<div>
<p>{{ solution.text }}</p>
<span #click="toggleMark">{{ markLabel }}</span>
</div>
</template>
<script>
// ...
computed: {
markLabel() {
return solution.isMarked ? 'Unmark' : 'Mark'
}
},
methods: {
toggleMark() {
// ???
}
}
</script>
I can store this state in Vuex for example, not in data() of index.vue. And I need to make an API call with to change isMarked value for a clicked solution of a task.
And here is the question: what is the best way to do that?
If solutionId were unique as a last resort I can iterate over all tasks in Vuex to find that solutionId and change state there. But in this case solutionId's are not unique for all tasks.
What solutions do I see:
Pass id of a task to each Solution with separate props. But in this case I need to create a lot of props for every component if Solution component is deep nested and it can be deep nested.
It will be the best just emit toggleMark event from Solution component and catch it in Task component but in my case of deep nesting Solution component I need to re-emit it in each parent component on a way to Task.
I can call a Vuex action just in Solution component to mutate isMarked. But I still need a task id to find a parent task which holds this clicked solution.
I can use an Event Bus, but it's not really favored in modern approaches.
I can make another computed method at index.vue and map each solution with their parent task id to make a new field taskId for example and pass the result object instead of tasks. But it looks a bit dirty way.
I can use provide / inject at least. And for this case it will be good solution. But what if Solution wasn't a child component of a Task and it will be in random place.
It's just a lightweight example of a problem. Sure it will be perfect just throw away SolutionsList and emit toggleMark event but intermediate components to Solution component could be a lot or it may not be a child of Task component for example.
Please tell me how can I solve this problem in a best way?

Nuxt, Strapi can't get media url

Could you guys please check my code and help me out.
I've uploaded some media to strapi and assigned them to each drink. Now I want to access that data with axios, I can get id, description, title etc. but I can not get URL from media.
Anyone could help please. Thanks!
NUXT:
<v-img
:src="'url(${http://localhost:1337}/drink.media.url)'"
></v-img>
<script>
async created() {
try {
const response = await axios.get("http://localhost:1337/drinks");
this.drinks = response.data;
} catch (error) {
this.error = error;
}
console.log(this.drinks)
},
};
</script>
JSON FROM POSTMAN
[
{
"id": 2,
"title": "Coca-Cola",
"description": null,
"price": 1,
"published_at": "2021-10-16T19:02:52.099Z",
"created_at": "2021-10-16T19:01:26.228Z",
"updated_at": "2021-10-16T19:02:52.124Z",
"unit": 250,
"media": [
"hash": "coca_cola_original_282x130_8baa6d1d20",
"ext": ".webp",
"mime": "image/webp",
"size": 37.01,
"url": "/uploads/coca_cola_original_282x130_8baa6d1d20.webp",
"previewUrl": null,
"provider": "local",
"provider_metadata": null,
"created_at": "2021-10-16T18:56:02.187Z",
"updated_at": "2021-10-16T18:56:02.206Z"
}
]
},
Thanks!
I don't know how you upload your images in strapi, but below I describe my method for uploading images of book covers in my strapi local-host and the way I access the image in my nuxt app. Maybe it is helpful for you to use it in your app.
First I made a new media field in my strapi "content-types builder" section and named it "bookCover". after that I upload my image in database for each book.
Second I used fetch() instead of created hook in my Nuxt app. below is the code of my Nuxt page script:
data() {
return {
books: [],
}
},
async fetch() {
this.books = await this.$axios.$get('http://localhost:1337/books');
}
Finally according to Nuxt documentation I used this code in my template part of my page:
<div>
<p v-if="$fetchState.pending">Fetching mountains...</p>
<p v-else-if="$fetchState.error">An error occurred :(</p>
<div v-else>
<!-- you must enter your index of drinks, for example here I access the first book image -->
<v-img max-width="250" :src="`http://localhost:1337${this.books[0].bookCover.url}`"></v-img>
</div>
</div>
I think if you use this method and use "fetch" hook and also modify the way you call src attribute in "v-img", you can access your image. Also notice that my variable names are different from your names and you must change them.

How to add structured data (json-ld) to Vue?

What’s the correct way to add json-ld to a vue app?
I’ve added some structured data but Google structured data testing tool does not detect the data at my url. However, if I view the source of url and paste into the testing tool it detects my data. What am missing?
One way you can add JSON-LD with VueJS is using v-html like in the example below.
You can find out more about structured data from Schema.org with VueJS in this article.
There is also this plugin on NPM that can help you do this.
<script>
export default {
data () {
const jsonld = {
"#context": "https://schema.org",
"#type": "BreadcrumbList",
"itemListElement": [{
"#type": "ListItem",
"position": 1,
"name": "Books",
"item": "https://example.com/books"
}, {
"#type": "ListItem",
"position": 2,
"name": "The Lord of the Rings",
"item": "https://example.com/books/the-lord-of-the-rings"
}]
}
return {
jsonld
}
}
}
</script>
<template>
<script v-html="jsonld", type="application/ld+json">
</template>