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

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")
}

Related

VueJS Delay on Update Props

So I have a simple component, let's say Transaction.vue, and inside it there is a child component named TransactionEditModal.vue.
In the Transaction.vue component, I call the method "openTransactionEditModal" by clicking a button.
The flow of the method is that I am editing my child property "this.chosenTransactionId" first before opening the modal using "showModal()".
// Transaction.vue
<button ref="editTransactionButton" v-on:click="openTransactionEditModal($event)" class="btn btn-primary">
<i style="pointer-events: none" class="far fa-edit"></i>
</button>
<TransactionEditModal ref="transactionEditModal" v-bind:transactionId="chosenTransactionId" />
<script>
data: function () {
return {
chosenTransactionId: "",
}
}
methods: {
openTransactionEditModal(event: MouseEvent) {
if (event.currentTarget instanceof HTMLButtonElement) {
this.chosenTransactionId = event.currentTarget.id;
console.log("Chosen Transaction Id is Updated", event.currentTarget.id);
}
var transactionEditModal: any = this.$refs.transactionEditModal; transactionEditModal.initializeExistingValues(this.transactions[6]);
transactionEditModal.showModal();
}
}
</script>
// TransactionEditModal.vue
<script>
props: {
transactionId: String,
},
methods: {
showModal() {
console.log("Child component props should have been updated, is it? ", this.transactionId);
var reportApproveModal: JQuery<HTMLDivElement> = $('#transactionEditModal');
reportApproveModal.modal('show');
},
}
</script>
But why is it that the props is only updated after second click?
Result:
// First Click
Chosen Transaction Id is Updated 5aa1dfc7-4b2f-4dbe-911f-98d70a2624f2 Transaction.vue:365
Child component props should have been updated, is it? TransactionEditModal.vue:36
// Second Click
Chosen Transaction Id is Updated 5aa1dfc7-4b2f-4dbe-911f-98d70a2624f2 Transaction.vue:365
Child component props should have been updated, is it? 5aa1dfc7-4b2f-4dbe-911f-98d70a2624f2 TransactionEditModal.vue:36
UPDATE 1
After using watch functionality in the child component I get this result:
Chosen Transaction Id is Updated 5aa1dfc7-4b2f-4dbe-911f-98d70a2624f2 Transaction.vue:365
Child component props should have been updated, is it? TransactionEditModal.vue:36
Means updated after watch 5aa1dfc7-4b2f-4dbe-911f-98d70a2624f2 TransactionEditModal.vue:42
And I am inferring that it is updated after showModal() is called, which I actually found it to be weird, maybe updating props are asynchronous?
// TransactionEdit.vue
<script>
watch: {
transactionId(newVal, oldVal) {
console.log('Means updated after watch', newVal, oldVal);
},
},
</script>
The reason why the props is only updated after second click is because Vue uses an asynchronous update queue to update the component, which means that when you update the props in the openTransactionEditModal method, it doesn't take effect immediately. Instead, it is added to the update queue and processed after the current event loop. When you click the button the first time, the props are updated and added to the update queue, but the showModal method is called before the update is processed, so the child component still has the old props. When you click the button the second time, the update is already processed and the child component now has the updated props.
Using the watch functionality in the child component you can check the updated value of the props and check the exact time it was updated.
You can refactor the code to make sure the child component receives the updated props before the showModal method is called:
// Transaction.vue
<button v-on:click="openTransactionEditModal($event)" class="btn btn-primary">
<i class="far fa-edit"></i>
</button>
<TransactionEditModal v-bind:transactionId="chosenTransactionId" #modal-open="showModal" ref="transactionEditModal" />
<script>
data: function () {
return {
chosenTransactionId: "",
}
},
methods: {
openTransactionEditModal(event: MouseEvent) {
if (event.currentTarget instanceof HTMLButtonElement) {
this.chosenTransactionId = event.currentTarget.id;
}
this.$nextTick(() => {
this.$refs.transactionEditModal.initializeExistingValues(this.transactions[6]);
this.$refs.transactionEditModal.$emit('modal-open');
});
},
showModal() {
var reportApproveModal: JQuery<HTMLDivElement> = $('#transactionEditModal');
reportApproveModal.modal('show');
},
}
</script>
// TransactionEditModal.vue
<script>
props: {
transactionId: String,
},
</script>
I have added a custom event 'modal-open' which is emitted from the parent component after updating the chosenTransactionId and it triggers the showModal() method. This way, we ensure that the child component has already received the updated props before the modal is opened.
In addition, I have also wrapped the initializeExistingValues() and $emit('modal-open') function inside this.$nextTick(() =>{}) to ensure that the value has been updated before calling the function.
Also, I have removed the unnecessary ref attributes and the type casting of HTMLButtonElement.
Are you sure you have made the correct transactionId reactive?
In Transaction.vue you have defined a reactive variable called transactionId.
But then in the code of that component you refer to a variable called chosenTransactionId.
I suggest you rationalise it to just transactionId for both.
Or, if you really need two separate variables, then add chosenTransationId to your data function in Transaction.vue.
How about only allowing the modal to exist, once the transactionId is set?
You could change
<TransactionEditModal ref="transactionEditModal" v-bind:transactionId="chosenTransactionId" />
to
<TransactionEditModal
v-if="chosenTransactionId"
ref="transactionEditModal"
v-bind:transactionId="chosenTransactionId"
/>
This is the more conventional approach. You could even send the initialization information in a v-bind parameter to the child. That would avoid the parent component having to directly call a function within the Modal.
In general it is better to avoid the pattern of calling functions in a child. Instead, make the child exist (with v-if) only when all the necessary information is available and ready to be passed to it.

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

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" />

passing events from parent to child react got undefined

Parent Class Component:
toggleFunction = (event, ...otherProps) => {
console.log(event) // EVENT GOT UNDEFINED
console.log(otherProps) // THIS IS DISPLAYING WHAT IS NEED!
}
<Child updateFunction={(event) => this.toggleFunction(event,"PROPS-1","PROPS-2")}>
Child Functional Component:
const Child = ({ updateFunction }) => {
... OTHER CODE HERE
<div onClick={() => updateFunction()}>Toggle</div>
}
My Problem is when i create event inside parent callback and pass to child as a method and execute parent toggleFunction in Parent Class. ONLY THE EVENT GOT UNDEFINED, OTHER PROPS IS DISPLAYING CORRECTLY
I found a solution by creating event inside child component and accessing inside Parent component, but that doesn't work for me as expected! Can anyone explain at what am i failing.
Thanks in advance!
You need to pass the event from onClick to updateFunction:
<div onClick={event => updateFunction(event)}>Toggle</div>
Or let onClick call it for you since the first parameter is the event:
<div onClick={updateFunction}>Toggle</div>

Is passing the "this" context through props an anti-pattern?

I have two components, a parent and a child like so:
class Parent extends React.Component {
shuffle() {
...
}
blur() {
...
}
next() {
...
}
previous() {
...
}
render() {
return (
<Child Parent={this} />
);
}
}
class Child extends React.Component {
constructor(props) {
super();
this.state = {};
this._onShuffleClick = this._onShuffleClick.bind(props.Parent);
this._onPreviousClick = this._onPreviousClick.bind(props.Parent);
this._onNextClick = this._onNextClick.bind(props.Parent);
}
_onShuffleClick(event) {
event.preventDefault();
this.shuffled ? this.shuffle(false) : this.shuffle(true); // I can call parents method here as the 'this' context is the 'Parent'.
this.blur(event.target);
this.setState({test "test"}); //I can set the parents state here
}
_onPreviousClick(event) {
event.preventDefault();
this.previous();
this.blur(event.target);
}
_onNextClick(event) {
event.preventDefault();
this.next();
this.blur(event.target);
}
render() {
return (
<a className="shuffle" key={1} onClick={this._shuffleOnClick}>{this.props.Parent.props.html.shuffle}</a>,
<a className="previous" key={2} onClick={this._previousOnClick}>{this.props.Parent.props.html.previous}</a>,
<a className="next" key={3} onClick={this._nextOnClick}>{this.props.Parent.props.html.next}</a>,
);
}
}
Is passing the context ('this' keyword) as a prop an anti-pattern?
Is setting the state of the parent from the child bad?
If I do this I then don't have to pass a lot of individual props to the child and I can also set the state of the parent from the child.
You can interact with the state of a parent from a child-component, but probably not the way you are trying to achieve this.
If you want to send in all props of the parent down to a child, you can do:
<Child {...this.props} />
This way, you don't need to specify each individual prop one at a time; instead, you just send them all in. Check out the spread operator here and here for more info. More info also on MDN:
The spread syntax allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) or multiple variables (for destructuring assignment) are expected.
If you want to access or modify the state of a parent from a child you have to do this slightly differently. Typically, you would create a function that does this interaction with the state in your parent and then send that function as a prop down to the child. Like this:
Parent:
_modifyState = (bar) => {
this.setState({foo: bar});
}
.....
<Child modifyState={this._modifyState} />
Child:
this.props.modifyState("Hello world!");
The above will set state.foo in the parent to the string Hello world! from the child component.
If you want access to all state variables, you could send it in as a prop to the child (the whole object) and then have a function (like above) which modifies the entire state (not just one property) - depends what you want really.
Well, it's mainly a bad usage of passing around the props, you could also go for {...props} instead, and I wouldn't want to pass it through the full name, you can also use let { props } = this; let parentProps = props.Parent.props. The question is also, why would you refer to parent props, that seems the bad practise, divide and conquor, only pass the props that are really needed, and do not assume in your child components that a certain parent component is available
When you pass event handlers down, let those eventhandlers be bound to your current this, but don't bind them in the child to an expected parent, a bit like this example
var StyledButton = React.createClass({
propTypes: {
clickHandler: React.PropTypes.func.Required,
text: React.PropTypes.string.required
},
render: function() {
let { clickHandler, text } = this.props;
return <button type="button" onClick={clickHandler}>{text}</button>;
}
});
var MyForm = React.createClass({
click: function() {
alert('ouch');
},
render: function() {
return <fieldset>
<StyledButton clickHandler={this.click} text="Click me" />
</fieldset>
}
})
ReactDOM.render(
<MyForm />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Yes I do think your code is bad practice. Now you chid components know about the parent component which makes your child impure.
When your parent implementation changes, the child components will break because of this.props.Parent.props.html.previous}.
I think each react component should update the parent by calling the parents functions passed by the props.
class Parent extends React.Component {
doSomethingBeacauseTheChildStateHasChanged() {
// function
}
render() {
<Child doSomething={doSomethingBeacauseTheChildStateHasChanged.bind(this)}/>
}
}
class Child extends React.Component {
render() {
<button onClick={this.props.doSomething}>Child button</button>
}
}
Note: I am not an expert and React beginner, treat this as an opinion rather than guideline.
I think yes cause you force particular implementation. What would you do if you wanted to have those methods in GrandParent? If you use props this modification is really easy, but with your implementation it would be pain in the ass.
There is also a feature called PropTypes. It's really great to make components reusable, but it's yet another thing you can't use if you do the things like you have proposed.
Maybe it is just me but this also creates a great confusion. You should pass everything you need as props.
Also setting parent state like this
this.setState({test "test"}); //I can set the parents state here
seems bad to me. I would rather pass a function from parent as a prop and bind parent before passing it down.
You can trigger a function in the Parent. This is the correct way to a children communicates with its parent.
class Parent extends React.Component {
shuffle(e) {
console.log(e.target);
return false;
}
render() {
return (
<Child onShuffle={this.shuffle} />
);
}
}
class Child extends React.Component {
render() {
return(
<a href='#' onClick={this.props.onShuffle}>Shuffle</a>
);
}
}
Child.propTypes = {
onShuffle: React.PropTypes.func
}

vuejs: v-if directive for event?

Can I react to an event in a vue template? Say a child component dispatches an event $dispatch('userAdded'), could I do something like this in the parent component:
<div class="alert alert-info" v-if="userAdded">
User was created!
</div>
or, if not, can I access variables of the child component?
<div class="alert alert-info" v-if="$refs.addView.form.successful">
User was created!
</div>
I tried both without success.
Also, while I'm here, is there an expressive way to hide elements after a certain amount of time? Something like (to hide after 2s):
<div class="alert alert-info" v-if="$refs.addView.form.successful" hide-after="2000">
User was created!
</div>
Thanks!
edit: wrote my own hide-after directive:
Vue.directive('hide-after', {
update: function(value) {
setTimeout(() => this.el.remove(), value);
}
});
<div class="alert alert-info" v-hide-after="2000">
This will be shown for 2 seconds
</div>
Yes you can but you need to take this approach.
Create a child that dispatches an event
In the parent component create an event listener for the event and also a data property that the event listener will set locally on the component instance
In the parent bind your v-if to the local data component
The code would look something like
parent
HTML
<div v-if="showAlert"></div>
Js
events: {
'alert.show': function () {
this.showAlert = true
},
'alert.hide': function () {
this.showAlert = false
}
},
data () {
return {
showAlert: false
}
}
Child
Js
methods: {
showAlert (show) {
show ? this.$dispatch('alert.show') : this.$dispatch('alert.hide')
}
}
The reason you should avoid using the $child and $parent is that it makes that component always depend on the fact that the parent will have the alert property and makes the child component lest modular
Since dispatch goes up until it hits a listener you can have several nested components in between the parent and child dispatching the alert control
UPDATE
Alternately, since you do not like the LOE of using events you can create a 2-way property on the child that either the parent or child can update
Example
Parent
HTML
<div v-if="showAlert"></div>
<child-component :show-alert.sync="showAlert"></child-component>
JS
data () {
return {
showAlert: false
}
}
Child
js
props: {
showAlert: {
type: Boolean,
twoWay: true
}
},
methods: {
showAlertInParent (show) {
this.$set('showAlert', show)
}
}
The whole idea of events is that you can react to them. But you want the reaction to pass by the model. You really don't want unrelated bits of markup listening and reacting 'independently'. $dispatch is deprecated. To do this now, do the following...
In the child component, emit an event as follows
this.$emit('didIt' {wasItAwful:'yep',wereYouScared:'absolutely'});
In the parent, you register the event listener with v-on, as an attribute of the child's tag...
<adventure-seeking-child v-on:did-it='myChildDidIt' />
Then, in the parent's methods, define your handler.
methods : { myChildDidIt : function(payload){ ... } }
Docs are here.

Categories

Resources