Vue.js Passing a variable in a loop to another component - vue.js

As a beginner in Vue.js,I am trying to create a Todo app, but problems seems to passing a variable to another component when looping.
i want to pass item to another embedded component.
Here what I have with all the components:
in main.js:
import Vue from "vue";
import App from "./App.vue";
import Todo from "./components/Todo";
import itemComponent from "./components/itemComponent";
Vue.config.productionTip = false;
Vue.component('todo', Todo);
Vue.component('itemcomponent', itemComponent);
new Vue({
render: h => h(App)
}).$mount("#app");
in App.vue:
<template>
<div id="app">
<todo></todo>
<img alt="Vue logo" src="./assets/logo.png" width="25%" />
<!-- <HelloWorld msg="Hello Vue in CodeSandbox!" /> -->
</div>
</template>
<script>
export default {
name: "App",
components: {
},
};
</script>
in Todo.vue:
<template>
<div>
<input type="text" v-model="nameme" />
<button type="click" #click="assignName">Submit</button>
<div v-for="(item, index) in result" :key="item.id">
<itemcomponent {{item}}></itemcomponent>
</div>
</div>
</template>
<script>
import { itemcomponent } from "./itemComponent";
export default {
props:['item'],
data() {
return {
nameme: "",
upernameme: "",
result: [],
components: {
itemcomponent,
},
};
},
methods: {
assignName: function () {
this.upernameme = this.nameme.toUpperCase();
this.result.push(this.nameme);
this.nameme = "";
},
},
};
</script>
in itemComponent.vue:
<template>
<div>
<input type="text" value={{item }}/>
</div>
</template>
<script>
export default {
props: {
item: String,
},
data() {
return {};
},
};
</script>
what is my mistake? thanks for help.

Quite a bunch of mistakes:
You should import single page components inside their parent components and register them there, not in main.js file. EDIT: Explaination to this is given in docs
Global registration often isn’t ideal. For example, if you’re using a build system like Webpack, globally registering all components means that even if you stop using a component, it could still be included in your final build. This unnecessarily increases the amount of JavaScript your users have to download.
You have a component registration in Todo.vue, but you have misplaced it inside data so its is just a data object that is not getting used, move into components.
In your loop you have item.id, but your item is just a plain string, it does not have an id property. Either change item to object with id property, or simply use the index provided in loop (not recommended).
Pass your item as a prop, not mustache template. So in Todo.vue replace {{ item }} with :item="item"
In your ItemComponent.vue you have mustache syntax once again in the attribute. Change value={{item }} to :value="item"
So here's the final code:
main.js
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
new Vue({
render: h => h(App)
}).$mount("#app");
in App.vue:
<template>
<div id="app">
<todo></todo>
<img alt="Vue logo" src="./assets/logo.png" width="25%" />
<!-- <HelloWorld msg="Hello Vue in CodeSandbox!" /> -->
</div>
</template>
<script>
import Todo from './components/Todo.vue';
export default {
name: "App",
components: {
Todo,
},
};
</script>
in Todo.vue:
<template>
<div>
<input type="text" v-model="nameme" />
<button type="click" #click="assignName">Submit</button>
<div v-for="(item, index) in result" :key="index">
<itemcomponent :item="item"></itemcomponent>
</div>
</div>
</template>
<script>
import { itemcomponent } from "./itemComponent";
export default {
props:['item'],
data() {
return {
nameme: "",
upernameme: "",
result: [],
};
},
methods: {
assignName: function () {
this.upernameme = this.nameme.toUpperCase();
this.result.push(this.nameme);
this.nameme = "";
},
},
components: {
itemComponent,
}
};
</script>
itemComponent.vue:
<template>
<div>
<input type="text" :value="item"/>
</div>
</template>
<script>
export default {
props: {
item: String,
},
data() {
return {};
},
};
</script>

Related

How can I accept only one component as slot in vue

I have a button-group component and I want this component only accepts Button component that I created as slots.
So this is my ButtonGroup component:
<template>
<div class="button-group">
<slot />
</div>
</template>
<script lang="ts">
export default {
name: 'ButtonGroup',
components: {
Button,
},
};
</script>
How can I accept only Button component as slot?
use render function
<script>
import {h} from 'vue';
export default {
name: 'ButtonGroup',
render() {
const buttons = []
for (let defaultElement of this.$slots.default()) {
// case: <button />
if (defaultElement.type === 'button') {
buttons.push(defaultElement)
}
// other component
// if (defaultElement.type.name === 'Button') {
// buttons.push(defaultElement)
// }
}
return h('div', {class: 'button-group'}, buttons)
}
};
</script>
I referenced here https://vuejs.org/api/render-function.html
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div class="button-group">
<slot name="button">
<Button />
</slot>
</div>
</template>
<script>
export default {
name: 'ButtonGroup',
components: {
Button,
},
};
</script>
//Use of ButtonGroup component`enter code here`
<ButtonGroup>
<template #button />
</ButtonGroup>

Vue JS Pass Data From Parent To Child Of Child Of Child

In Vue.js, how to correctly pass data from parent component to a chain of multi level child components?
You have a few options:
Props
Event Bus
Vuex
Provide/Inject
Find out more here: https://www.smashingmagazine.com/2020/01/data-components-vue-js/
#RoboKozo listed some really great options for you, it just depends on what you're doing with the data.
If it's static data (and you like to be modular), you can always make use of mixins.
From the Vue.js docs:
Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.
Example 1: Mixins
File/Folder Setup:
/src/mixins/defaultDataMixin.js // Call this whatever you want
/src/mixins/defaultDataMixin.js:
export const default defaultData {
data() {
return {
property1: "Foo",
property2: "Bar"
}
}
};
Now, on any component you want to have access to property1 or property2, you can just import your mixin.
/src/pages/MyComponent.vue
<template>
<div id="wrapper">
<div class="container">
<div class="row">
<div class="col">
{{ property1 }}
</div>
<div class="col">
{{ property2 }}
</div>
</div>
</div>
</div>
</template>
<script>
import { defaultData } from "#/mixins/defaultDataMixin"
export default {
mixins: [defaultData],
}
</script>
Example 2: Sharing data via Props & Emit
Props down, Emit up
Perhaps you return some data in a Parent component that you wish to pass down to be used in a child component. Props are a great way to do that!
File/Folder Setup
/src/pages/ParentComponent.vue
/src/components/ChildComponent.vue
ParentComponent.vue
<template>
<div id="wrapper">
<div class="container">
<div class="row">
<div class="col">
<child-component :defaultDataProp="defaultData" #emitted-text="helloWorld">
</child-component>
</div>
</div>
</div>
</div>
</template>
<script>
import { defaultData } from "#/mixins/defaultDataMixin"
import ChildComponent from "#/components/ChildComponent.vue"
export default {
mixins: [defaultData],
components: {
ChildComponent
},
data() {
return {
emittedText: "",
}
},
methods: {
helloWorld(e){
this.emittedText = e;
}
}
}
</script>
ChildComponent.vue
<template>
<div id="wrapper">
<div class="container">
<div class="row">
<div class="col">
{{ defaultDataProp }}
</div>
</div>
<div class="row">
<div class="col">
<button #click="emitData">Emit Data</button>
</col>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
defaultDataProp: {
type: String
}
},
data() {
return {
text: "Hello, world!"
};
},
methods: {
emitData() {
this.$emit('emitted-text', this.text);
}
}
}
</script>
Cool, right?
These are just a couple of ways you can share data and functions between components.
** Edit **
Example 3: Vuex state + mapGetters
I just noticed you tagged this state as well, so I'm assuming you have already installed and are using Vuex.
While it's not "passing" the data from parent to child directly, you could have the Parent component can mutate a state that a child component is watching.
Let's imagine you want to return the user information and then have access to that data across multiple components.
File/Folder setup:
/.env
/src/main.js
/src/store/store.js
/src/store/modules/user.js
/src/mixins/defaultDataMixin.js
/src/util/api.js
/src/pages/ParentComponent.vue
/src/components/ChildComponent.vue
main.js
import Vue from "vue";
import Vuex from "vuex";
import { store } from "#/store/store";
// ... add whatever else you need to import and use
Vue.use(vuex); // but make sure Vue uses vuex
// Vue can "inject" the store into all child components
new Vue({
el: "#app",
store: store, // <-- provide the store to the vue instance
// ..
});
.env:
VUE_APP_API_URL='http://127.0.0.1:8000'
api.js:
import axios from "axios";
import { store } from "#/store/store";
const apiClient = axios.create({
baseURL: process.env.VUE_APP_API_URL,
});
export default apiClient;
user.js:
import apiClient from "#/util/api"
import { store } from "../store"
const userModule = {
state: () => ({
user: {
firstName: "",
}
}),
mutations: {
setUser(state, payload) {
state.user = payload;
}
},
actions: {
async loadUser({ commit, dispatch }) {
try {
const user = (await apiClient.get("/user")).data;
commit("setUser", user); // mutate the vuex state
} catch (error) {
dispatch("logout");
}
},
logout({ commit }) {
commit("setUser", {});
}
},
getters: {
firstName: (state) => {
return state.user.firstName;
}
}
};
export default userModule;
store.js
import Vue from "vue";
import Vuex from "vuex";
import userModule from "#/store/modules/user"; // Grab our newly created module
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
userState: userModule,
},
});
defaultDataMixin.js
import { mapGetters } from "vuex";
export const default defaultData {
data() {
return {
property1: "Foo",
property2: "Bar"
}
},
// We can make use of mapGetters here
computed: {
...mapGetters(["firstName"]),
}
};
Now, both Parent and Child have access to the centralized state and can mutate it. For example, let's call the loadUser() from the parent and watch for the state change on the child component.
ParentComponent.vue
<template>
<div id="wrapper">
<div class="container">
<div class="row">
<div class="col" v-if="firstName != ''">
{{ firstName }}
</div>
<div class="col">
<button #click="mutateState">Click Me</button>
</div>
</div>
<div class="row">
<div class="col">
<child-component #emitted-text="helloWorld">
</child-component>
</div>
</div>
</div>
</div>
</template>
<script>
import { defaultData } from "#/mixins/defaultDataMixin"
import ChildComponent from "#/components/ChildComponent.vue"
export default {
mixins: [defaultData],
components: {
ChildComponent
},
methods: {
// our new method to mutate the user state
mutateState() {
this.$store.dispatch("loadUser");
},
helloWorld(e){
this.emittedText = e;
}
}
}
</script>
ChildComponent.vue
<template>
<div id="wrapper">
<div class="container">
<div class="row">
<div class="col">
{{ defaultData }}
</div>
</div>
<div class="row">
<div class="col">
<button #click="emitData">Emit Data</button>
</col>
</div>
</div>
</div>
</template>
<script>
import { defaultData } from "#/mixins/defaultDataMixin"
export default {
mixins: [defaultData],
data() {
return {
text: "Hello, world!"
};
},
methods: {
emitData() {
this.$emit('emitted-text', this.text);
}
},
// Let's watch for the user state to change
watch: {
firstName(newValue, oldValue) {
console.log('[state] firstName state changed to: ', newValue);
// Do whatever you want with that information
}
}
}
</script>

Vuejs change component element value dynamicaly

I have a component in common between two other components A,B. This shared Comp has a button and its name changes depending on the component i use. How do I set the name dynamic?
I thought v-model solved the problem
What am I missing?
App.vue:
<test-a></test-a>
<test-b></test-b>
sharedComp.vue:
<template>
<div>
{{ btnValue }}
<input type="button" v-model="btnValue" />
</div>
</template>
<script>
export default {
data() {
return {
btnValue: "",
};
},
};
</script>
CompA.vue
<template>
<div>
<shared-comp
v-for="(item, index) in 3"
:key="index"
:value="'A'"
></shared-comp>
</div>
</template>
<script>
import SharedComp from "./SharedComp.vue";
export default {
components: { SharedComp },
};
</script>
CompB.vue
<template>
<div>
<shared-comp :value="'B'"></shared-comp>
</div>
</template>
<script>
import SharedComp from "./SharedComp.vue";
export default {
components: { SharedComp },
};
</script>
You have to define the properties you pass to your component inside of the 'sharedComp'.
Try something like:
<template>
<div>
{{ value }}
<input type="button" v-model="value" />
</div>
</template>
<script>
export default {
props: ['value'],
};
</script>
For further information on Props in Vue check the documentation page: https://v2.vuejs.org/v2/guide/components-props.html

Passing parents props to slot in Vue,js

I have in my application a DocumenContainer component which has multiple ChartContainer components. The ChartContainer has a slot in which I put various types of Charts (bar chart, Line Chart etc.). I would like to pass the data isOuput to the child component which is a slot
ChartContainer (simplified):
<template>
<div class="card-body">
<slot v-slot="isOutput"></slot>
</div>
</template>
<script>
export default {
data() {
return {
isOutput : false,
}
}
</script>
DocumentContainer:
<chart-container title="Stats Model" v-slot="slotProps" :documentId="id">
{{slotProps.isOuput}}
<v-bar-chart :docId="id"></v-bar-chart>
</chart-container>
I tried passing the isOutput to the parent (DocumentContainer) with v-slot. The problem right now is that I'm only able to print {{slotProps.isOutput}}. I would like to pass that slotProps.isOutput as a props to the <v-bar-chart> and
<v-bar-chart :isOuput="slotProps.isOutput" :docId="id"></v-bar-chart>
is giving me undefined in the bar-chart props.
Is there a simpler way than to pass the data to the parent and to the child? How can I achieve this?
I think this is something to do with the context
It will work if you use v-bind instead
<v-bar-chart v-bind="{ isOutput: slotProps.isOutput, docId: id }"></v-bar-chart>
Example code
const Component1 = {
template: `
<div>
<h2>Component 1</h2>
<button #click="isOutput = !isOutput">Toggle</button>
<slot :isOutput="isOutput"></slot>
</div>
`,
data() {
return {
isOutput: false,
}
}
};
const Component2 = {
props: ['isOutput'],
template: `
<div>
<h2>Component 2</h2>
isOutput: {{String(isOutput)}}
</div>
`
};
new Vue({
el: '#app',
components: {
Component1,
Component2
}
});
<script src="https://unpkg.com/vue#2.6.10/dist/vue.min.js"></script>
<div id="app">
<h1>Home</h1>
<Component1>
<template v-slot="slotProps">
isOutput: {{String(slotProps.isOutput)}}
<Component2 v-bind="{ isOutput: slotProps.isOutput }">
</Component2>
</template>
</Component1>
</div>

Reusable nested VueJS components

Is it possible to declare a component inside another component in Vue.JS?
this is what i'm trying to do:
<!-- this is declared inside some-component.vue file -->
<script>
export default {
components:{
'cmptest' : {
template:'#cmptest',
props:['mprop']
}
},
data : () => ({
val:'world'
})
};
</script>
<template>
<div>
<template id="cmptest">
{{mprop}}
</template>
<cmptest mprop="hello"></cmptest>
<cmptest :mprop="val"></cmptest>
</div>
</template>
I'd like to avoid globally registering the child component if possible (with Vue.component(...))
In other words, I'd like to specify child's <template> inside the parent component file (without doing a huge line template:'entire-html-of-child-component-here')
Sure.
Like this:
https://jsfiddle.net/wostex/63t082p2/7/
<div id="app">
<app-child myprop="You"></app-child>
<app-child myprop="Me"></app-child>
<app-child myprop="World"></app-child>
</div>
<script type="text/x-template" id="app-child2">
<span style="color: red">{{ text }}</span>
</script>
<script type="text/x-template" id="app-child">
<div>{{ childData }} {{ myprop }} <app-child2 text="Again"></app-child2></div>
</script>
new Vue({
el: '#app',
components: {
'app-child': {
template: '#app-child',
props: ['myprop'],
data: function() {
return {
childData: 'Hello'
}
},
components: {
'app-child2': {
template: '#app-child2',
props: ['text']
}
}
}
}
});