How to pass props in VueJS? - javascript

I have a simple setup in my App which consists of 2 components. The HelloWorld component and then a dialog component. The dialog component accepts props passed from the HelloWorld component. The data in HelloWorld is being rendered by looping over an array. What i am trying to achieve is when i click on one of the elements i want the dialog to be pre filled with the name and age of the element i clicked on. I am not sure how i can do that?
Check out this CodeSandbox for the complete Setup.
This is my Vuex Store:-
state: {
userData: [
{
name: "Sam",
age: "24"
},
{
name: "Max",
age: "28"
}
],
dialog: false
},
getters: {
getUserData: state => state.userData,
getDialog: state => state.dialog
},
mutations: {
openDialog(state) {
state.dialog = true;
}
}
This is my HelloWorld Component:-
<template>
<v-container>
<v-layout justify-center>
<v-card>
<v-card-text>
<div v-for="user in getUserData" :key="user.age">
<span>{{user.name}}</span>
<span>{{user.age}}</span>
<v-icon #click="openDialog">create</v-icon>
</div>
</v-card-text>
</v-card>
</v-layout>
<Dialog/>
</v-container>
</template>
<script>
import { mapGetters, mapMutations } from "vuex";
import Dialog from "./Dialog";
export default {
name: "HelloWorld",
components: {
Dialog
},
data() {
return {};
},
computed: {
...mapGetters({
getUserData: "getUserData"
})
},
methods: {
...mapMutations({
openDialog: "openDialog"
})
}
};
</script>
This is my Dialog Component:-
<template>
<v-dialog v-model="getDialog" max-width="300px">
<v-card>
<v-card-text>
<v-text-field v-model="title"></v-text-field>
<div class="mt-5">{{age}}</div>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
import { mapGetters } from "vuex";
export default {
props: {
title: {
type: String
},
age: {
type: Number
}
},
data() {
return {};
},
computed: {
...mapGetters({
getDialog: "getDialog"
})
}
};
</script>
Appreciate all the help. Thank you.

<div v-for="(user, index) in getUserData" :key="user.age">
<span>{{user.name}}</span>
<span>{{user.age}}</span>
<v-icon #click="openDialogAndGetCurrentUser(index)">create</v-icon>
</div>
openDialogAndGetCurrentUser(index) {
this.openDialog();
/* define these in your data to be passed to child */
this.currentuserName = this.getUserData[index].name;
this.currentuserAge = this.getUserData[index].age;
}
<Dialog
:title="currentuserName"
:age="currentuserAge"/>

Related

How to create a reusable Vuetify Snackbar?

I'm trying to create a reusable alert/snackbar component. I declared them like this:
Snackbar.vue
<template>
<v-snackbar transition="true" timeout="2000" :show="`${show}`" :color="`${color}`" absolute top outlined right>
<strong>
{{ message }}
</strong>
</v-snackbar>
</template>
<script>
export default {
name: 'Snackbar',
props: {
show: String,
color: String,
message: String
}
}
</script>
<template>
<v-snackbar transition="true" timeout="2000" :show="`${show}`" :color="`${color}`" absolute top outlined right>
<strong>
{{ message }}
</strong>
</v-snackbar>
</template>
<script>
export default {
name: 'Snackbar',
props: {
show: String,
color: String,
message: String
}
}
</script>
I imported, and pass probs to them like so :
import Snackbar from '../../../components/Snackbar'
<Snackbar v-if="alert" color="alertColor" message="alertMessage" />
<Snackbar show="alert" color="alertColor" message="alertMessage" />
I've tried the 2 lines above, and these are the value of those 3 variables
alert = true
alertColor = green
alertMessage = create.vue?f4fe:631 DB error - insert_marketing_campaign: Duplicate entry 'TEST' for key 'MARKETING_CAMPAIGN_AK'
Result
I see no error in console, but see no snackbar either. pls help
8!
You aren't passing props values correctly, you have to do this instead:
ps. for illustration, i added an event listener(update) to deal with a close button(remove if you dont want to use)
<Snackbar :show="alert" :color="alertColor" :message="alertMessage" v-on:update="alert = $event" />
Snackbar.vue
I changed a few things here too:
the way you are using props in the attributes works, but if dont need to concatenate other values, keep it simple
prop show must be Boolean
added a beautiful button if you want to use
v-model controls snackbar exhibition, but you can't use the prop show directly on v-model, that's why we have a computed property "show2"(very creative) that read(get) the value of prop show and emit a custom event "update" if you want to change it(set)
<template>
<v-snackbar transition="true" timeout="2000" v-model="show2" :color="color" absolute top outlined right>
<strong>
{{ message }}
</strong>
<v-btn color="red" text #click="show2 = false">Close</v-btn>
</v-snackbar>
</template>
<script>
export default {
props: {
show: Boolean,
color: String,
message: String,
},
computed: {
show2: {
get() {
return this.show;
},
set(value) {
this.$emit("update", value);
},
},
},
};
</script>
Hope it helps! ;)
Check this codesandbox I made: https://codesandbox.io/s/stack-71244492-vuex-snackbar-klfunf?file=/src/store/index.js
You can set up a global snackbar in your App.vue file and update it with Vuex. In your vuex store you set up an snackbar object and create a simple update mutation and show action.
// store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
snackbar: { active: false, color: "", message: "", timer: 4000 }
},
mutations: {
UPDATE_SNACKBAR(state, snackbar) {
state.snackbar = snackbar;
}
},
actions: {
showSnack({ commit }, { message, color, timer }) {
commit("UPDATE_SNACKBAR", {
active: true,
color: color,
message: message,
timer: timer
});
}
}
});
Then in your App.vue you set it up like this
<v-snackbar
v-model="snackbar.active"
:color="snackbar.color"
:right="true"
:bottom="true"
:timeout="snackbar.timer"
>
{{ snackbar.message }}
<template #action="{ attrs }">
<v-btn
color="white"
text
v-bind="attrs"
#click="snackbar.active = false"
>
Close
</v-btn>
</template>
</v-snackbar>
<script>
import { mapState } from "vuex";
export default {
name: 'App',
data: () => ({
drawer: false,
}),
computed: {
...mapState(["snackbar"])
}
}
</script>
Then you can trigger your snackbar from any component simply by doing this
// views/Home.vue
this.$store.dispatch('showSnack', {
message: 'Testing, main page.',
color: 'green darken-1'
})
// views/About.vue
this.$store.dispatch('showSnack', {
message: 'Hello from about!',
color: 'red darken-1',
timer: 2000
})

Vue - how to pass array in this case?

This is my sample code. I wanted to create a small form builder.
I will have many select fields. How can I pass an array into a loop? My code doesn't work, but I hope you know what effect I want to get.
<template>
<div>
<div v-for="(input, index) in formBuilder" :key="index">
<h1>{{ input.name }}</h1>
<div>
Options:<br />
{{ formBuilder.options }}
</div>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
return {
formBuilder: [
{
name: "Name",
options: this.versions,
},
{
name: "Host",
options: this.countryList,
},
],
};
},
computed: mapState(["versions", "countryList"]),
};
</script>
EDIT.
Below, I have added a version that works. But can it be done in a better way? And is this the correct way?
It works:
<template>
<div>
<div v-for="(input, index) in formBuilder" :key="index">
<h1>{{ input.name }}</h1>
<div>
Options:<br />
{{ input.options }}
</div>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
return {
formBuilder: [
{
name: "Name",
options: [],
},
{
name: "Host",
options: [],
},
],
};
},
created() {
this.formBuilder[0].options = this.versions;
this.formBuilder[1].options = this.countryList;
},
computed: mapState(["versions", "countryList"]),
};
</script>
As https://stackoverflow.com/users/381282/michal-lev%c3%bd mention. computed property is your "correct solution".
computed: {
...mapState(['versions', 'countryList']),
formBuilder() {
return [
{ name: "Name", options: this.versions },
{ name: "Host", options: this.countryList },
]
}
}
Explaination:
If you put the code in created it will only prepare formBuilder once when the component created.
If you use computed the formBuilder will be recomputed everytime this.versions or this.coutryList get updated.

Vue : Cannot pass the value from parent to child component

Hi I was learning vue and I am trying to make a todo app, I cannot show the todo list items in my todo app, I can't figure out what is the problem is. This is my App.vue:
<template>
<TodoList id="app" v-bind:todos="todos" />
</template>
<script>
import TodoList from './components/list.vue';
export default {
name: 'App',
data() {
return {
todos: [
{
id: 1,
title: 'Go workout',
completed: false
},
{
id: 2,
title: 'Do laundry',
completed: false
},
{
id: 3,
title: 'Cook food',
completed: false
}
]
};
},
components: {
TodoList
}
};
</script>
This is my list.vue:
<template>
<div>
<h2>Todo List</h2>
<ul>
<li v-bind:key="todo.id" v-for="todo in todos">
<Todo v-bind:todo="todo" />
</li>
</ul>
</div>
</template>
<script>
import Todo from './todo.vue';
export default {
name: 'TodoList',
components: {
Todo
},
created() {
console.log(this);
}
};
</script>
This component contains the Todo item, this is todo.js:
<template>
<div>{{ todo.title }}</div>
</template>
<script>
export default {
name: 'Todo',
props: ['todo'],
created() {
console.log('todo', this);
}
};
</script>
But todo is probably not being created, as the created lifecycle is not being called here. Help me figure what is the problem is.
You forgot the props attribute in your TodoList component:
export default {
name: 'TodoList',
props: ['todos'],
components: {
Todo
},
created() {
console.log(this);
}
};

VueJS mutating od duplicate v-model (:value, #input)

I have a problem with using the v-model in my own component. Namely, I dont want to use State or Bus.
At present, the component returns the single value correctly in App.js, it duplicates itself.
I can not deal with it, please help and explain me the problem.
Thanks a lot!
App.js:
<template>
<b-container>
<SectionSelector :AddSection="AddSection"/>
<component
v-for="(section, index) in sections"
:key="index"
:is="section.type"
:sectionIndex="index"
:sectionData="section[index]"
#sectionDataEmit="sectionDataEmit"/>
</b-container>
</template>
<script>
import SectionSelector from './components/SectionSelector.vue';
import FullText from './components/sections/FullText.vue';
import FullImage from './components/sections/FullImage.vue';
import ImageRightTextLeft from './components/sections/ImageRightTextLeft.vue';
import ImageLeftTextRight from './components/sections/ImageLeftTextRight.vue';
export default {
data() {
return {
sections: []
}
},
methods: {
AddSection(sectionData) {
this.sections.push(sectionData);
},
updateSection(sectionIndex, sectionData) {
this.sections[sectionIndex] = sectionData;
},
sectionDataEmit(emitData) {
// eslint-disable-next-line
console.log(emitData.position, emitData.content);
this.sections[emitData.position].fields.text = emitData.content;
}
},
components: {
SectionSelector,
FullText,
FullImage,
ImageRightTextLeft,
ImageLeftTextRight
}
}
</script>
SectionSelector.vue:
<template>
<b-row>
<b-dropdown id="dropdown-1" text="Select" class="m-md-2">
<b-dropdown-item v-for="(section, index) in sections"
:key="index"
#click="PushSection(index)"> {{ section.type }} </b-dropdown-item>
</b-dropdown>
</b-row>
</template>
<script>
export default {
props: ['AddSection'],
data() {
return {
sections: [
{
type: 'FullText',
fields: {
text: ''
}
},
{
type: 'FullImage',
fields: {
url:''
}
},
{
type: 'ImageRightTextLeft',
fields: {
img: '',
text: ''
}
},
{
type: 'ImageLeftTextRight',
fields: {
img: '',
text: ''
}
}
]
}
},
methods: {
PushSection(index) {
this.AddSection(this.sections[index])
}
}
}
</script>
FullText.vue:
<template>
<b-row>
<h3>Full text {{ sectionIndex+1 }}</h3>
<b-textarea
:value="sectionData"
#input="sectionDataEmit"></b-textarea>
</b-row>
</template>
<script>
export default {
props: ['sectionIndex', 'sectionData'],
methods: {
sectionDataEmit(value) {
let emitData = {
position: this.sectionIndex,
content: value
}
this.$emit('sectionDataEmit', emitData)
}
}
}
</script>
At present, the code causes duplication sections.fields.text (visible in the chrome extension Vue)
There is a place in your code that uses object[index]=. Do not do that with Vue data objects. Instead use Vue.set(object, index, value).
updateSection(sectionIndex, sectionData) {
Vue.set(sections,sectionIndex, sectionData);
},
This is based on the rule that Vue cannot react to new properties added to the object after data is initialized.

How do I make a state change?

I made two different components.
in one v-navigation-drawer
the second has a button that changes the States of v-navigation-drawer in vuex.
When on small screens, there is a call to v-navigation-drawer, and after clicking on the gray area, the state in vuex does not change.
enter code here
drawer.vue
<template>
<v-navigation-drawer
fixed
:value="drawer"
app
:class="getSettingsTheme"
:mini-variant="mini"
>
<b>menu</b>
</v-navigation-drawer>
</template>
sitebar.vue
<template>
<v-toolbar fixed app clipped-right :class="getSettingsTheme">
<v-tooltip right>
<v-btn slot="activator" icon #click.stop="drawerMut">
<v-icon class="theme--light grey--text" color="grey lighten-1">menu</v-icon>
</v-btn>
<span>Show/Hide drawer</span>
</v-tooltip>
</v-toolbar>
</template>
vuex
export default new Vuex.Store({
state: {
drawer: true,
},
mutations: {
drawerMut(state) {
state.drawer = !state.drawer
}
},
getters: {
drawer(state) {
return state.drawer
},
}})
You can read the drawer state from the store and update to the store
in Vue we have computed setter please go through the docs.
if you use :value then on backdrop click the state/value of the drawer won't be updated, so use v-model with drawer as computed with setter and getter.
export default {
data() {
return {
mini: true,
getSettingsTheme: ''
}
}
computed: {
drawer: {
get() {
return this.$store.getters['drawer'];
},
set(val) {
this.$store.commit('drawerMut', val);
},
}
}
}
<template>
<v-navigation-drawer fixed
v-model="drawer"
app
:class="getSettingsTheme"
:mini-variant="mini">
<b>menu</b>
</v-navigation-drawer>
</template>

Categories