Is this the right approach to create a component? - vue.js

I made a view to show some contact information for the user:
<template>
<div v-for="user in users" class="user">
<div class="userInformation">
<img :src="user.photo" />
<div class="userName">
<h3>{{ user.age }}</h3>
<p>{{ user.gender }}</p>
</div>
</div>
<div class="button-wrapper">
<a href="#">
<button #click="$router.push(`/user/${user.id}`)">User Profile</button>
</a>
</div>
</div>
</template>
<style>
</style>
users is an array that holds all users which I fetch from the backend.
I want to create a component so that I can re-use the user card in other classes and don´t have to include the markup. I tried it the following way but I'm stuck at the button to redirect the user and the img because I don´t know how to use named slots there.
<template>
<div class="user">
<div class="userInformation">
<img />
<div class="userName">
<h3>{{ age }}</h3>
<p>{{ gender }}</p>
</div>
</div>
<div class="button-wrapper">
<a href="#">
<button>User Profile</button>
</a>
</div>
</div>
</template>
<script>
export default {
name: "UserCard",
props: [
"age",
"gender"
]
};
</script>
Another problem is that I have to re-create the fetch method for my users in other classes to access the user information. Would there be a better way of doing this?
// fetch user data from backend and create users array
...
<div v-for="user in users" :key="user.name">
<UserCard
:age="`${user.age}`"
:gender="`${user.gender}`"
/>
</div>
Is this the right approach to create a reusable component?

You're headed in the right direction for your component. If you wanted a named slot for the button you could use something like this.
Child Component
<template>
...
<slot name="button">
<!-- default/fallback content can be provided, if the parent does
not provide slot content the button-wrapper div will appear -->
<div class="button-wrapper">
<a href="#">
<button>Default Button</button>
</a>
</div>
</slot>
</div>
</template>
Parent
<div v-for="user in users" :key="user.name">
<UserCard
:age="user.age"
:gender="user.gender">
<template v-slot:button>
<div>some custom button here {{ user.phone }}</div>
</template>
</UserCard>
</div>
Also compilation scope (Vuejs v2 guide) is an important thing to keep in mind with slots - "Everything in the parent template is compiled in parent scope; everything in the child template is compiled in the child scope."
In terms of fetching your users, that's a separate issue. Look into something like Vuex or other ways of managing shared state if you find yourself constantly having to fetch users in various components

Related

Vue JS: How to make two Vue Instances to work together on the same page?

I created a recipe website, you can find it here https://hungry-vegetarian.com/.
The tab on the index page is a vue instance. I'm trying to create a search bar that is also a vue instance.
The problem occurs when I try to display searched recipes on the index page. It just shows the search result on top of the search bar.
Ideally, only searched recipes should appear on the tab just like they are now but without any indications on top of the tab like Breakfast, Salad, Bakery, etc.
I don't know how to make two objects work together. For now, they work separately without communicating with each other.
SEARCH
<div id="search">
<div class="main-search">
<h1 class="search-question">What are you in the mood for?</h1>
<div class="search-box">
<input type="text" v-model="searchQuery" class="input-search" placeholder="Bread">
<button class="btn-search"><i class="fa fa-search"></i></button>
</div>
</div>
<div class="container">
<div class="recipe" v-for="post in filteredList">
<div v-if="searchQuery">
<a v-bind:href="post.url" class="recipe-card__card-link" target="_blank">
<img v-bind:src="post.image" alt="" class="recipe-card__image"/>
<div class="recipe-card__text-wrapper">
<h2 class="recipe-card__title">{{post.name}}</h2>
<div class="recipe-card__details-wrapper">
<p class="recipe-card__excerpt">{{post.body}}</p>
<a v-bind:href="post.url" class="recipe-card__read-more">Read more <i class="fa fa-arrow-right"></i></a>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
tab
<main id="tab">
<header>
<nav>
<ul>
<div id="arrow">
<span></span>
<span></span>
<span></span>
</div>
<li v-for="(tab, tabName) in tabs" :key="tabName">
<button class="tab" #click="setTabActive(tabName)" :class="{'active': tabName === activeTab}">
<span class="tab-copy">{{ tabName }}</span>
<span class="tab-background">
</span>
</button>
</li>
</ul>
</nav>
</header>
<article>
<div class="container">
<transition name="fade" mode="out-in" appear :duration="500">
<tab-content v-for="(tabContent, t) in tabs" :data="tabContent" :key="'content'+t" v-if="t === activeTab" inline-template>
<div class="content-wrapper">
<div class="recipes" v-for="(recipe, i) in data.recipes" :key="i">
<a v-bind:href="recipe.url" class="recipe-card__card-link"></a>
<img :src="recipe.image" alt="" class="recipe-card__image">
<div class="recipe-card__text-wrapper">
<h2 class="recipe-card__title">{{recipe.name}}</h2>
<div class="recipe-card__details-wrapper">
<p class="recipe-card__excerpt">{{recipe.body}}</p>
<a v-bind:href="recipe.url" class="recipe-card__read-more">Read more <i class="fa fa-arrow-right"></i></a>
</div>
</div>
</div>
</div>
</tab-content>
</transition>
</div>
</article>
</main>
I tried to combine instances search and tab into just one instance tab, but it always gives me a Vue warning:
[Vue warn]: Property or method "searchQuery" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Also, I feel like I can have just the wrong approach to this problem I am trying to solve. Please, any suggestions are welcome. I am stuck.

use v-slot to pass property to slots component - Vue 3

I´m struggeling with passing properties into a slots component.
Here some code to explain my problem:
App template:
<template>
<ModalContainer v-model="visibleModal">
<create-dir-modal />
</ModalContainer>
</template>
ModalContainer:
<template>
<div class="modal-container" v-if="modelValue" #click.self="close">
<slot :close="close" />
</div>
</template>
(close is a defined method here)
CreateDirModal:
<template v-slot="slot">
<div class="create-dir-modal-container">
<i class="button" #click="slot.close">close</i>
<div>
<i>folder</i>
<input placeholder="Name" v-model="name" />
<span></span>
<i class="button">done</i>
</div>
</div>
</template>
I am trying to execute the close method from the ModalContainer in the CreateDirModal.
Here is my problem: slot is not defined in CreateDirModal template.
Am I just using it wrong or is there a way to fix my problem?

VueJs 3 checkbox v-model on array of objects not working properly

I am having a hard time understanding why my v-model isn't working correctly
I have an 'service' object which contains a property 'actions' of type IAction[]
I also declared an object actions which is an array of IAction and am currently trying to bind checkBoxes to the actions array, but it is not working.
I feel like i am missing something obvious here but would need a little help understanding what it is.
Here is the relevant code
<script lang="ts">
let actions = [] as IAction[];
</script>
<template>
<div v-for="action in service.Actions" :key="action.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ action.EnumName }}
</span>
<input v-model="actions" :value="action" type="checkbox" />
</div>
</div>
</div>
</template>
I would appreciate any feedback as I am relatively new to VueJs,
Thank you
I think you might not understand what you are doing in code, so I wrote examples.
Bad Code:
<script lang="ts">
let actions = [] as IAction[];
</script>
<template>
// here you iterate thro array and assign to action variable
<div v-for="action in service.Actions" :key="action.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ action.EnumName }}
</span>
// Here you using actions with "s" on end so you using empty array declered in script
<input v-model="actions" :value="action" type="checkbox" />
</div>
</div>
</div>
</template>
If you are getting some data from service.Actions use them! v-model will override those actions if they are ref() or `reactive().
Example:
<script lang="ts">
let actions = [] as IAction[];
</script>
<template>
<div v-for="item in service.Actions" :key="action.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ item.EnumName }}
</span>
<input v-model="item.is" :value="action" type="checkbox" />
</div>
</div>
</div>
</template>
If service.Actions is only array actions you want to add to array in script actions v-model is not a way you do that!
Probably code you need:
<script lang="ts">
const actions = ref([]) // Use refs everywhere !!! A specially in forms.
function deleteItem() {
// ToDo delete item from actions array
}
</script>
<template>
<div v-for="item in service.Actions" :key="item.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ item.EnumName }}
</span>
<button #click="actions = [...actions, item]">ADD</button>
</div>
</div>
</div>
<div>
<div v-for="{ item, index } in actions" :key="item.id">
<span>{{ item.EnumName }}</span><button #click="deleteItem(index)">X</button>
</div>
</div>
</template>
As Mises pointed out, the v-model has to be a part of the same object as the v-for, so i just put my services and the actions array in an object
let foo = { services: serviceStore.services, actions: [] as IAction[] }

Vue add or remove enclosing div

I have a html structure like this:
<template>
<div>
<div class="container">
<somecontent>
<someothercontent>
</div>
</div>
</template>
However, depending on the layout, I want to remove the container div so the structure looks like this:
<template>
<div>
<somecontent>
<someothercontent>
</div>
</template>
Note that the div is completely added or removed, not just the class name. v-if doesn't work here because it would also show or hide the content that is enclosed by the div. What's the simplest solution for that?
You can use
v-if ... v-else
<template>
<div v-if="condition">
<div class="container">
<somecontent>
<someothercontent>
</div>
</div>
<div v-else>
<somecontent>
<someothercontent>
</div>
</template>

Adding a content inside of a vue component

I have a vue component Jumbotron.vue:
<template lang="html">
<div class="jumbotron" style="border-radius:0px; color:#fff;">
<div class="container">
</div>
</div>
</template>
And other page component Main.vue:
<template>
<div class="root">
<navbar></navbar>
<jumbotron>
<h1 class="display-4">I want to add here, to the jumbotron's div container some h1 and paragraph</h1>
<p class="lead ">Like this paragraph</p>
</jumbotron>
<words></words>
</div>
</template>
But i cant add content to the jumbotron, because its wrong. I dont want to add content(p and h1) inside of the Jumbotron.vue, because i want to use Jumbotron.vue more than 1 time with different content inside it. Is this possible?
This is done with slots.
<template lang="html">
<div class="jumbotron" style="border-radius:0px; color:#fff;">
<div class="container">
<slot></slot>
</div>
</div>
</template>
Everything you put inside the jumbotron component in the parent will be rendered in the slot.