trigger function in child.vue after clicking button in parent.vue - javascript

I'm working with BootstrapVue.
I have a method in my parent.vue where I pass a value (this.propsIndex) to my child.vue.
Now I want to use this value each time it will be clicked in a method of my child.vue - but how can I trigger my function and make it working?
Thank You very much!
If it's possible I want to avoid using watch
my parent.vue
<template>
<div v-for="(id, index) in inputs" :key="index">
<b-button #click="deleteViaIndex(index)">Delete</b-button>
<child :indexProps="indexProps" />
</div>
<div>
<b-button #click="addInput()">Add Input</b-button>
</div>
</template>
<script>
methods: {
deleteViaIndex(index) {
this.propsIndex= index;
},
addInput() {
this.inputs.push({})
},
},
data() {
return {
inputs: [{}],
propsIndex: '',
}
}
</script>
my child.vue (script)
props: ["propsIndex"],
methods: {
deleteViaParentIndex() {
//HERE I WANT TO USE IT EVERY TIME IT WILL BE CLICKED IN MY PARENT.VUE
//BUT FOR NOW IT'S NOT DOING ANYTHING WHEN I CONSOLE.LOG(this.propsIndex)
}
}

Aside from the naming mismatch mentioned by Marcin, you can access a child component from a parent component using a ref in the template:
<child ref="childrenComponents" :props-index="propsIndex" />
Since you have multiple of the child components inside a v-for, this makes the childrenComponents in $refs an array of components. To call deleteViaParentIndex on all of them, you need to iterate through them:
deleteViaIndex(index) {
this.propsIndex = index;
this.$refs.childrenComponents.forEach(child => child.deleteViaParentIndex());
}
There's one more optimization you can make.
Since you're using propsIndex only to pass an index that the child component uses, and you already have the index as a param in deleteViaIndex, you can simply pass that to the child as a param during the deleteViaParentIndex function call, thus removing the need for the propsIndex data altogether:
in parent.vue:
deleteViaIndex(index) {
this.$refs.childrenComponents.forEach(child => child.deleteViaParentIndex(index));
}
in child.vue:
deleteViaParentIndex(index) {
// operations using index
}

Looks like a naming mismatch. In a child component, you have a prop propsIndex, yet in a parent template you are passing indexProps.
When passing props, you have to remember, that prop name is always in the first part, and the value you are passing should go next. https://v2.vuejs.org/v2/guide/components-props.html#Passing-Static-or-Dynamic-Props
Furthermore, since HTML attribute names are case-insensitive, you should pass a prop this way:
<child :props-index="indexProps" />

Related

Vuex pass mutable object as prop, call mutation with the passed object as argument

Let's say I have a Vuex State which contains an array of objects which I want to mutate in the component. I iterate over the array and spawn a Component for each Object which takes the Object as a prop. Inside the component I call Vuex mutations with the Object passed down as a direct argument. For example:
Parent
<template>
<ItemComponent
v-for="(item, index) in items"
:key="index"
:index="index"
:item="item"
/>
</template>
<script>
import ItemComponent from 'ItemComponent.vue';
export default {
components: {
ItemComponent
},
computed: {
items() {
return this.$store.getters.items;
}
}
};
</script>
Child:
<template>
</template>
<script>
export default {
props: {
item: {
type: Object,
required: true
}
},
methods: {
changeItemProp() {
this.$store.dispatch('changeItemXValue', {this.item, 'newValue'});
}
}
};
</script>
Store:
// ...
mutations: {
changeItemXValue(state, { item, value }) {
item.x = value
}
}
It works, yes. But I'm pretty sure this is an Antipattern, right?
// ...
mutations: {
changeItemXValue(state, { item, value }) {
state.items.find((i) => i === item).x = value
}
}
This doesn't seem much better.
My question: Is this the way to go if I want to encapsulate my mutable data in a subcomponent, or is this considered an anti-pattern? In which case I would like to know what the proper way is to handle this case. I initially prepared vuex exactly to handle this case but I'm not sure if this is quite the right way. Thank you.
Well, there are multiple ways of doing this, but I'll explain a recommended way of doing.
What you're doing right now is that, you're fetching items from store, passing them to a child component (By iteration) then mutating following mutation,
// ...
mutations: {
changeItemXValue(state, { item, value }) {
state.items.find((i) => i === item).x = value
}
}
First of all, you don't need to mutate this item directly, instead you should create an action that finds the item for you and then mutate it using item id or index.
So what you need to do is that, create an action, when changing value of item, dispatch that action, and pass item id or even item to that action. Inside that action, find the item you want to mutate and then commit mutation, inside your mutation, simply write
state.items[itemIndex] = newValue

How to emit an event from Vue.js Functional component?

As the title of the question, this context is not available in the functional component. So if I have to emit an event, how can I do that?
For example in below code snippet:
<template functional>
<div>
<some-child #change="$emit('change')"></some-child>
</div>
</template>
My functional component doesn't have this context and hence $emit is not available. How can I bubble-up this event?
Child Component
<template functional>
<button #click="listeners['custom-event']('message from child')">
Button from child
</button>
</template>
Parent Component
<template>
<div>
<child-component #custom-event="call_a_method" />
</div>
</template>
See it in action on codesandbox
Do you want to emit the event from the vue instance?
export default {
functional: true,
render(createElement, { listeners }) {
return createElement(
"button",
{
on: {
click: event => {
const emit_event = listeners.event_from_child;
emit_event("Hello World!Is this the message we excpected? :/");
}
}
},
"Pass event to parent"
);
}
};
See it also a sandbox example here
This is explained in the docs Passing Attributes and Events to Child Elements/Components:
If you are using template-based functional components, you will also have to manually add attributes and listeners. Since we have access to the individual context contents, we can use data.attrs to pass along any HTML attributes and listeners (the alias for data.on) to pass along any event listeners.
At the most basic level, you can delegate all listeners like this:
<some-child v-on="listeners"></some-child>
If you only want to bind the change listener, you can do:
<some-child #change="listeners.change"></some-child>
but this will fail if listeners.change is undefined/null (not provided to the functional component).
If you need to handle the situation where there is no change listener, then you can do this:
<some-child #change="listeners.change && listeners.change($event)"></some-child>
otherwise you would have to settle by writing the render function by hand, since I don't think it is possible to conditionally assign the change listener to <some-child> in the template of a functional component. (Or maybe you can? I'm not sure.)
If you want to pass event listener conditionally you can do it inside functional component template like this:
v-on="listeners.change ? { change: listeners.change } : null"
The issue of conditionally attaching listeners is discussed here
a component with jsx:
export default {
name: "MyText",
functional: true,// functional component
props: {
value: {
type: [String, Number],
default: ""
}
},
render(h, context) {
const { props } = context;
// with jsx
// return (
// <button
// onClick={() => {
// console.log(context.listeners);
// context.listeners.input(Math.random().toString(36));
// context.listeners["my-change"](Math.random().toString(36));
// context.data.on.change(Math.random().toString(36));
// }}
// >
// {props.value}
// </button>
// );
// or use h function
return h(
"h1",
{
on: {
// emit some event when click h1
click: () => {
// has value prop has has input event auto
// event name come what event u listen in parent component
console.log(context.listeners);
context.listeners.input(Math.random().toString(36));
context.listeners["my-change"](Math.random().toString(36));
context.data.on.change(Math.random().toString(36));
}
}
},
props.value
);
}
};
conext.listeners is just an alias for context.data.on.
in parent componet, you should listen my-change and change, or has error.
event name inside component comes what event u listen in parent component
<MyText
v-model="value"
#change="change"
#my-change="myChange"
#u-change="uChange"
/>
vue 2.6.11 works well.
see the codesandbox online
Parent:
<Child #onFunction="handleFunction">
and this is the child component:
Child
<template functional>
<div>
<some-child #change="execute"></some-child>
</div>
</template>
methods:
execute(){
#emit("onFunction")
}

How to bind dynamic props to dynamic components in VueJS 2

I'd like to know how I can iterate a list of component names (which come from an AJAX call to an API server) and render them as components, and pass relevant properties to each component (i.e. bind their properties dynamically).
So far I have managed to iterate a JSON list of items that represent components, and successfully render these components. What I'd like to do now is bind the properties for each component using v-bind.
In the example below, the item-one component would receive the image property with the item1.jpg value; and the item-two component wouldn't receive any properties.
<template>
<div v-for="item in items">
<component :is="Object.keys(item)[0]" :v-bind="???"></component>
</div>
</template>
<script>
import ItemOne from '../components/item-one'
import ItemTwo from '../components/item-two'
export default {
components: {
ItemOne,
ItemTwo
},
asyncData () {
return {
items: [
{ 'item-one': { 'image': 'item1.jpg' } },
{ 'item-two': { } }
]
}
}
}
</script>
I tried using :v-bind="Object.values(Object.keys(item)[0])" but I get the attribute v-bind="[object Object]" in the rendered element.
First, you need to get rid of the colon before v-bind. The colon is a short-hand for v-bind when prefixed to an attribute. But, when passing an object of key pairs to bind, you simply use the v-bind directive.
Second, you are not correctly referencing the properties of each item. You can reference them like this:
v-bind="item[Object.keys(item)[0]]"
You wouldn't need to use Object.keys if you changed the structure of your items property a bit:
items: [{
type: 'item-one',
props: { 'image': 'item1.jpg' },
}, {
type: 'item-two',
}]
This way, by explicitly labeling the component type and properties beforehand, your template would be much easier to understand:
<div v-for="item in items">
<component :is="item.type" v-bind="item.props"></component>
</div>

Assign unique id to component as it renders using Redux

I recently started learning how to use Redux as a way to manage state in my applications and I like it so far, but I'm having trouble assigning props dynamically to to children of a parent component.
Here is what I am working with, basic parent with children scenario:
<Layout>
<Child />
<Child />
<Child />
</Layout>
I want each child to have a unique id so childProps.id for the first child would be 1, the second child would be 2 and so on.
First here is the assignId function I want to fire when the component is rendered:
export function assignId(number){
return{
type: "ASSIGN",
payload: number
}
}
Here is the reducer listening for this function:
export default function reducer(state={
id: 0
}, action){
switch (action.type){
case "ASSIGN": {
return {...state, id: action.payload}
}
}
return state
}
Finally the <Child /> component itself:
import React from "react";
import { activeBox, beginSession, unselect, assignId } from '../actions/boxActions'
export default class Child extends React.Component {
componentWillMount(){
var i = 1
this.props.dispatch(assignId(i));
i++
}
render(){
return (
<div className="box"></div>
)
}
}
componentWillMount works. I have var i = 1 it sets childProps.id to 1 for all of my <Child /> components. Simply, how would I make a function that makes each childProps.id unique for each <Child /> component based on it's order within the <Layout /> parent?
I think you're adding complexity with zero value. The id attribute of a DOM element has for sure to be unique, so you're on a right track, however
in React, you don't manage elements by their IDs; take a look at references to corresponding DOM elements instead,
generating a globally unique identifier comes at cost of maintaining that generating function, and
if you plan to have an arbitrary number of Child components coming from somewhere and being reflected from an iterable, why not using Array#map?
So
render() {
const childElements = [1, 2, 3];
return (
<Layout>
<Child id="1" />
<Child id="2" />
<Child id="3" />
</Layout>
);
}
turns into
render() {
const childElements = [1, 2, 3];
return (
<Layout>
{childElements.map((id, index) => (
<Child
key={index}
id={id} />
))}
</Layout>
);
}
From your code, I can see that you want to dispatch an action to assign an element with an ID, while that ID isn't even stored anywhere. If it really isn't stored and used for any purpose, there's no need in it.
A general rule in React is to make things simple, not complicated. You have data, you reflect this data into DOM using components (and the hierarchy they form), and that's it. If you need to listen to DOM events, you use onClick, onChange and other props that are bound to DOM event listeners, and that's it. Once you want to use a lib that works with DOM, try to stick to refs instead of ids, because those are guaranteed to refer to existing DOM element, and reference by id may lead to nowhere when a component is being updated.
Contrary to what others are saying, it makes good sense to assign IDs to array items in the store. When you render them you can pass this ID as the key prop to each child component. This starts to make great sense when you insert, delete or reorder array items. React will use this key to help it find the right component that matches the array item. This can speed up rendering massively when store item indices change around.
This is the reason the key prop exists and it's also why you should not simply use the index of the item. The index isn't the same thing as the identity.
Now, to generate and store such a unique ID, do something like this:
let nextId = 0;
function addItem(item) {
return {
type: 'ADD_ITEM',
payload: {
...item,
id: nextId++
}
};
}
function reducer(state = [], action) {
switch action.type {
case 'ADD_ITEM':
return [...state, action.payload]
break;
default:
return state;
}
}
You can then assign this ID to the key prop of each child. It'll be unique for the session and that's all you probably need. If not you can generate any other ID you like, such as a GUID. In any case, the important thing is that you want the ID to be a part of the item from the beginning and that the ID is not and should not be assigned to the component, but to the item the component is going to display. The component assumes the identity of the item found in the store and not the other way around.
At the parent level, get your children IDs in an array, loop through them, and assign the id as a prop of the child.
Something like this:
<Layout>
{childrenIds.map((i) => { return <Child id={i} />} )}
</Layout>

Can you force Vue.js to reload/re-render?

Just a quick question.
Can you force Vue.js to reload/recalculate everything? If so, how?
Try this magic spell:
vm.$forceUpdate();
//or in file components
this.$forceUpdate();
No need to create any hanging vars :)
Update: I found this solution when I only started working with VueJS. However further exploration proved this approach as a crutch. As far as I recall, in a while I got rid of it simply putting all the properties that failed to refresh automatically (mostly nested ones) into computed properties.
More info here: https://v2.vuejs.org/v2/guide/computed.html
This seems like a pretty clean solution from matthiasg on this issue:
you can also use :key="someVariableUnderYourControl" and change the key when you want to component to be completely rebuilt
For my use case, I was feeding a Vuex getter into a component as a prop. Somehow Vuex would fetch the data but the reactivity wouldn't reliably kick in to rerender the component. In my case, setting the component key to some attribute on the prop guaranteed a refresh when the getters (and the attribute) finally resolved.
Please read this
http://michaelnthiessen.com/force-re-render/
The horrible way: reloading the entire page The terrible way:
using the v-if hack The better way: using Vue’s built-in
forceUpdate method The best way: key-changing on your
component
<template>
<component-to-re-render :key="componentKey" />
</template>
<script>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
</script>
I also use watch: in some situations.
Try to use this.$router.go(0); to manually reload the current page.
Why?
...do you need to force an update?
Perhaps you are not exploring Vue at its best:
To have Vue automatically react to value changes, the objects must be initially declared in data. Or, if not, they must be added using Vue.set().
See comments in the demo below. Or open the same demo in a JSFiddle here.
new Vue({
el: '#app',
data: {
person: {
name: 'Edson'
}
},
methods: {
changeName() {
// because name is declared in data, whenever it
// changes, Vue automatically updates
this.person.name = 'Arantes';
},
changeNickname() {
// because nickname is NOT declared in data, when it
// changes, Vue will NOT automatically update
this.person.nickname = 'Pele';
// although if anything else updates, this change will be seen
},
changeNicknameProperly() {
// when some property is NOT INITIALLY declared in data, the correct way
// to add it is using Vue.set or this.$set
Vue.set(this.person, 'address', '123th avenue.');
// subsequent changes can be done directly now and it will auto update
this.person.address = '345th avenue.';
}
}
})
/* CSS just for the demo, it is not necessary at all! */
span:nth-of-type(1),button:nth-of-type(1) { color: blue; }
span:nth-of-type(2),button:nth-of-type(2) { color: red; }
span:nth-of-type(3),button:nth-of-type(3) { color: green; }
span { font-family: monospace }
<script src="https://unpkg.com/vue"></script>
<div id="app">
<span>person.name: {{ person.name }}</span><br>
<span>person.nickname: {{ person.nickname }}</span><br>
<span>person.address: {{ person.address }}</span><br>
<br>
<button #click="changeName">this.person.name = 'Arantes'; (will auto update because `name` was in `data`)</button><br>
<button #click="changeNickname">this.person.nickname = 'Pele'; (will NOT auto update because `nickname` was not in `data`)</button><br>
<button #click="changeNicknameProperly">Vue.set(this.person, 'address', '99th st.'); (WILL auto update even though `address` was not in `data`)</button>
<br>
<br>
For more info, read the comments in the code. Or check the docs on <b>Reactivity</b> (link below).
</div>
To master this part of Vue, check the Official Docs on Reactivity - Change Detection Caveats. It is a must read!
Use vm.$set('varName', value).
Look for details into Change_Detection_Caveats
Sure .. you can simply use the key attribute to force re-render (recreation) at any time.
<mycomponent :key="somevalueunderyourcontrol"></mycomponent>
See https://jsfiddle.net/mgoetzke/epqy1xgf/ for an example
It was also discussed here: https://github.com/vuejs/Discussion/issues/356#issuecomment-336060875
<my-component :key="uniqueKey" />
along with it use this.$set(obj,'obj_key',value)
and update uniqueKey for every update in object (obj) value
for every update this.uniqueKey++
it worked for me this way
So there's two way you can do this,
You can use $forceUpdate() inside your method handler i.e
<your-component #click="reRender()"></your-component>
<script>
export default {
methods: {
reRender(){
this.$forceUpdate()
}
}
}
</script>
You can give a :key attribute to your component and increment when want to rerender
<your-component :key="index" #click="reRender()"></your-component>
<script>
export default {
data() {
return {
index: 1
}
},
methods: {
reRender(){
this.index++
}
}
}
</script>
In order to reload/re-render/refresh component, stop the long codings. There is a Vue.JS way of doing that.
Just use :key attribute.
For example:
<my-component :key="unique" />
I am using that one in BS Vue Table Slot. Telling that I will do something for this component so make it unique.
Using v-if directive
<div v-if="trulyvalue">
<component-here />
</div>
So simply by changing the value of trulyvalue from false to true will cause the component between the div to rerender again
Dec, 2021 Update:
You can force-reload components by adding :key="$route.fullPath".
For Child Component:
<Child :key="$route.fullPath" />
For router-view tag:
<router-view :key="$route.fullPath" />
However, :key="$route.fullPath" only can force-reload the components of the different route but not the components of the same route. To be able to force-reload the components of the same route as well, we need to add "value" with an array to :key="$route.fullPath" and change "value". So it becomes :key="[$route.fullPath, value]" and we need to change "value".
*We can assign Array to :key=.
<template>
<Child
:key="[$route.fullPath, value]" // Can assign "Array" to ":key="
#childReload="reload" // Call #click="$emit('childReload')" in
/> // Child Component to increment the value.
</template>
OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR
<template>
<router-view
:key="[$route.fullPath, value]" // Can assign "Array" to ":key="
#routerViewReload="reload" // Call #click="$emit('routerViewReload')"
/> // in Child Component to increment the value.
</template>
<script>
export default {
name: "Parent", components: { Child, },
data() {
return {
value: 0,
};
},
methods: {
reload() {
this.value++;
}
}
}
</script>
However, to keep using both "$route.fullPath" and "value" causes some error sometimes so only when some event like Click happens, we use both "$route.fullPath" and "value". Except when some event like Click happens, we always need to use only "$route.fullPath".
This is the final code:
<template>
<Child
:key="state ? $route.fullPath : [$route.fullPath, value]"
#childReload="reload" // Call #click="$emit('childReload')" in
/> // Child Component to increment the value.
</template>
OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR
<template>
<router-view
:key="state ? $route.fullPath : [$route.fullPath, value]"
#routerViewReload="reload" // Call #click="$emit('routerViewReload')" in
/> // Child Component to increment the value.
</template>
<script>
export default {
name: "Parent", components: { Child, },
data() {
return {
state: true,
value: 0,
};
},
methods: {
reload() {
this.state = false;
this.value++;
this.$nextTick(() => this.state = true);
}
}
}
</script>
Unfortunately, there are no simple ways to force-reload components properly in Vue. That's the problem of Vue for now.
This has worked for me.
created() {
EventBus.$on('refresh-stores-list', () => {
this.$forceUpdate();
});
},
The other component fires the refresh-stores-list event will cause the current component to rerender
<router-view :key="$route.params.slug" />
Just use key with your any params its auto reload children..
I found a way. It's a bit hacky but works.
vm.$set("x",0);
vm.$delete("x");
Where vm is your view-model object, and x is a non-existent variable.
Vue.js will complain about this in the console log but it does trigger a refresh for all data. Tested with version 1.0.26.
Worked for me
data () {
return {
userInfo: null,
offers: null
}
},
watch: {
'$route'() {
this.userInfo = null
this.offers = null
this.loadUserInfo()
this.getUserOffers()
}
}
The approach of adding :key to the vue-router lib's router-view component cause's fickers for me, so I went vue-router's 'in-component guard' to intercept updates and refresh the entire page accordingly when there's an update of the path on the same route (as $router.go, $router.push, $router.replace weren't any help). The only caveat with this is that we're for a second breaking the singe-page app behavior, by refreshing the page.
beforeRouteUpdate(to, from, next) {
if (to.path !== from.path) {
window.location = to.path;
}
},
Except page reload method(flickering), none of them works for me (:key didn't worked).
and I found this method from old vue.js forum which is works for me:
https://github.com/vuejs/Discussion/issues/356
<template>
<div v-if="show">
<button #click="rerender">re-render</button>
</div>
</template>
<script>
export default {
data(){
return {show:true}
},
methods:{
rerender(){
this.show = false
this.$nextTick(() => {
this.show = true
console.log('re-render start')
this.$nextTick(() => {
console.log('re-render end')
})
})
}
}
}
</script>
Add this code:
this.$forceUpdate()
For anyone still looking around, there's a package for this now.
https://github.com/gabrielmbmb/vuex-multi-tab-state
All I had to do was install it and add it to my plugins in main.ts (as it shows on that page) and it did exactly what I wanted.
If your URL changes as well when if the component is loaded you can just use it in the :key attribute. This works especially well if you use it on the router-view tag directly. And this commes with the added benedit of the key being a value that is actually tied to the content of the page instead of just some random number.
<router-view :key="this.$route.path"></router-view>
If you are using router-view or Vue Router, you can directly use the key feature
<router-view :key="$route.path"></router-view>
This will tell the router view to re-render the page every time the path is changed.

Categories

Resources