vue, emitting vs passing function as props - javascript

Let's say I have a button component that is imported in several other components. I want the child component to not be coupled to any one type of logic that happens when the button is clicked. So I want to hold that logic in the various components that leverage this button component.
I think there are at least 2 ways of going about this.
Have the child emit an event to the parents, and then let the parents define the handler.
Define the handlers in the parents and pass it down as props to the button component.
I'm used to doing the latter in React. Is there a best practice in vue for this situation?

The Vue philosophy is props down, events up. The first option follows that closer as the event itself is emitted (up) to the parent and then handled.
Also within a Vue SFC you have the added benefit of prefixing the bound attribute with a v-on (or #) which describes its intent as an event traveling up and not a v-bind (or :) which implies it's a prop even though its really a callback to an event.

Vue.js events are callbacks, they are not DOM events. You can verify this, since you add a custom name to the event listener and not a DOM event name (click, focus...), and there is no event object passed to the function, unless you specify an $event argument in the $emit call.
Events
Pros
For libraries: keeps it lighter and clients have more flexibility on methods usage
Helpful Vue devtools event logging
Allow global listener (this.$root.on), although this can be better enhanced by Vuex.js.
Differentiated syntax: : for props and # for events/methods
Cons
Less explicit, harder to debug (fail silently if there are no listeners or the event name is misspelled)
Props
Pros
More explicit, are declarative, can be defaulted, required, validated, what turns them easier to debug (runtime errors or compilation errors in TypeScript)
Cons
Have to include props validation so you don't have to check if a function() prop exists before calling it (but using props validation is a good practice anyway...)
Conclusion
Looks like the approaches are more convention and personal preference over anything else, although I think that if it wasn't for the Vue.js documentation giving preference to the events approach, everybody would be gladly using props only, which in my opinion is better (clearer).
Props can do everything events do, except for a few cases (like $root event listening pattern - noting Vuex.js replaces this feature and is preferred for scalability), with the advantage they are more explicit, debuggable and check-prone.
Summarized from: https://forum.vuejs.org/t/events-vs-callback-props/11451

As a newbie perspective migrated from React, I don't know why #event even exists (or like the answers above - being the standard). I can't declare which events a component would $emit?, but I can easily see which props are passed down. And by a good naming, I will be able to know which one is actually a callback event.

Best Practice
Best practice would be option number 1. You can see this practice being used in the official documentation: https://v2.vuejs.org/v2/guide/components.html#Sending-Messages-to-Parents-with-Events
Performance
As long as you pass a reference to a function to be executed when using the event bus or passing down as a prop, you should see almost no performance difference.
Example using option number 1
You can use this.$emit('eventName', dataToSend, ...) to send the data to the parent component that would then listen on the component like this <my-component #eventName="yourHandler" />. You would then be able to use different logic for each button.
I have created a fiddle for a multi-select component that implements this: https://jsfiddle.net/wkdL0xbc/
// HTML
<div id="app">
<multi-choice :items="myItems" #selected="alert($event)"></multi-choice>
<multi-choice :items="myItems" #selected="sayIsCool"></multi-choice>
</div>
// JavaScript
const multiChoice = {
template: '<div class="multi-choice"><span v-for="item in items" #click="select(item)">{{ item }}</span></div>',
props: ['items'],
methods: {
select(item) {
this.$emit('selected', item);
}
}
};
new Vue({
el: "#app",
data() {
return {
myItems: [
'Homer',
'Marge',
'Bart'
],
}
},
components: {
multiChoice: multiChoice
},
methods: {
sayIsCool(item) {
alert(item + ' is cool!')
}
}
})

You’re looking for “Transparent Wrappers”
Vue's customs event works different from a native DOM event. So you need to attach .native property to the event
But if you want the event to happen on the child, then you define a computed property that will return and an object of listeners. And now you won't
By default, attributes not defined as props will be added to the root element of the view
So you can set inheritAttrs: false and then bind the $attrs to the child and it then becomes the target for those attributes
Now you don't have to think about what the root component is.
Chris Fritz does a great job explaining how they work in his 7 secret patterns talk. Starts around 21:44 https://youtu.be/7lpemgMhi0k?t=21m44s

I think this depends if we don't give a better context.
Consider props vs event is like pull vs push, quite similar to any pub-sub system.
When passing props, we inject (push) the dependencies of parent context to child context, and then child context can be polluted by parent context, not just holding the weak ref to the parent, any effect from a parent is now also executed within child context. This is also coupled between parent-child.
Consider event pulling, which parent is listening event from child, now every event data is preferably a copy value instead of ref, we don't have coupling issue between parent-child. In case we have event, we have also control by queue or custom modifier so that the usage from parent is easier to maintain (like we don't have to maintain debounce, throttle on parent context, but expect by event modifier, it should be done within child context, in this case is the Button component).

Related

What's the correct way to dispatch/forward events across nested in components in Svelte?

I'm curious, what's the best way to forward or dispatch events across multiple levels in component tree in Svelte JS?
Say I have App.Svelte, some intermediate number levels, each containing a child component, and Modal.Svelte. If I want to forward or dispatch an event from Modal to App, what's the right way to do this?
As I understand it, event forwarding in Svelte will traverse up the component tree and forward the event to the first parent that references the forwarded event. (Is this the correct interpretation?)
And using event dispatch approach, each nested component would need to 1/ import createEventDispatcher, 2/ create a dispatcher variable, 3/ define a function, which dispatches the event. Then parent's would need to import the function and reference it inside a tag, such as <p>. (Is this correct?)
If I'm correct on both of the above, I'm wondering if there isn't a more streamlined approach, eg connecting the event to stores, which would effectively flatten the component tree such that any component could receive the forwarded event. Though I imagine that this could induce some hard to debug behavior if multiple components reference the same forwarded event.
To forward a event from a child component or DOM element to the parent, you just have to define an on:event without handler. E.g.
<button on:click >
<Component on:open >
No need to use createEventDispatcher just for forwarding.
If you want to share events more widely, you can create an EventTarget and send events through that. Since subscriptions would not happen in the template via on:event, you have to make sure to remove listeners again. You can also use a context to just expose the object to a sub-tree of the component hierarchy.
Alternatively, events can be dispatched directly to DOM elements, such events will bubble up the entire element hierarchy (if enabled in the event options).
someElement.dispatchEvent(
new CustomEvent('my-event', { bubbles: true })
);
If you dispatch events to the window, directly or via bubbling, you can subscribe to those more conveniently using svelte:window:
<svelte:window on:my-event={() => ...} />
(These handlers are removed automatically when the component is destroyed.)
You might consider instead using accessor functions. In App.svelte, define a function that manipulates your top-level variables. Then place that function in an accessor object and pass that down as a prop to all your components that may need it.
<script>
[App.svelte]
let myVar, myObj, myWhatever
function updateMyVal(newValue) {
myVar = newValue
}
function mergeMyObject(mergeObj) {
myObj = {...myObj, ...mergeObj}
}
let accessorObject = {
updateMyVal: updateMyVal,
mergeMyObject: mergeMyObject
}
</script>
<ChildComponent {accessorObject} {myVar} {myObj} />
. . .
[ChildComponent.svelte]
<script>
export let accessorObject, myVar, myObj
accessorObject.updateMyVal(1234)
accessorObject.mergeMyObject({newProp: newVal})
</script>
And so forth... this has the advantage of pushing changes to application-wide variables from the top down, which I've found to work better for complex SPAs than a web of events, two-way-bindings or stores, at least in my limited experience.

Is it possible to send an event from one component to another without using a global EventBus in Vue.js?

I have two components that do not have a parent - child relationship. They are not related to each other. But I am trying to make it so that when an event is emitted from one component then the other component can listen to that event and perform an action.
Here is an example, let's say that I have a component called "reset-component" and at some point it will emit an event called "reset".
<reset-component #reset="actionReset" />
And I also have a "grid-component" that should listen to the "reset" action emitted by the "reset-component" and perform some action.
<grid-component />
What are my options do accomplish this? The only solution that I can think of is to emit a global event using an EventBus and then have the grid-component listen to that global event. But is that a good idea? Isn't that more of an anti-patern?
// Reset component
EventBus.$emit('reset')
// Grid component
created()
{
EventBus.$on('reset', () => {
doSomething()
})
}
You can use Subject to accomplish this. See this link for more information: Vue.js + RxJS - Communicating Between Components with Observable & Subject
You actually have multiple options. But as you already answered yourself a global event bus is one of them
In your main file (assume something like main.js) you could do
Vue.prototype.$bus = new Vue()
By doing so you can now access this.$bus in every component e.g.
methods: {
dispatchReset () {
this.$bus.$emit('reset')
}
}
And other components can listen to it via
created () {
this.$bus.$on('reset', this.doReset)
}
Vuex is another option that was already mentioned by #Armen Armus in the comments. You could hold a global state for your application and dispatch events from within every component you want. However, if you really only want a few events like a reset I don't see a reason to add vuex.
Another option could also be to simply hold a "state" in your root Vue instance / component, but this would require you do pass down props and pass up events from within every view / component - and that would be quite annoying.
When passing props to child components and emitting events to parent components become hard because of distance or app complexity you can use VueX :
https://vuex.vuejs.org/
Yes you can use vm.$on and vm.$emit within components.
In your reset-component trigger the event from any method or element
this.$emit('reset')
and then you can then listen it,
<reset-component #reset="actionReset" />
You can also just add to the instances hooks:
ResetComponent.$on("reset", doSmth)
vm.$on
But i guess this will create a relation.

Pattern for sending events from parent to child component in Blaze

Let's assume we have the following abstract setup:
<template name="parent">
{{> child childProps}}
</template>
and a helper function:
Template.parent.helpers({
childProps() {
const instance = Template.instance();
return {
// whatever props we want
};
}
});
I am wondering what's the best way to allow parent component to send events to it's child component.
There are at least two methods I can think of:
Don't send events and only control the child through props. This is probably what some people would recommend doing. The problem is that this approach requires child to track it's Template.currentData() context and react accordingly to potential changes, which makes the implementation much more complicated and error prone.
Create instance.emitter = new EventEmitter() in the onCreated hook and pass it to the child data context, so that it can add listeners for certain events. This seems more flexible and less error prone, but still a little complicated as the child needs to properly manage listeners cleanup not only in it's lifecycle methods, e.g. onDestroy but also as a result to current data context changes.
Does anyone have other ideas how this pattern could be implemented in Blaze? Is there some kind of standard way of doing it, or maybe some packages that already solve this problem?

React/Flux implementation technique is unclear for when a parent component needs to pull strings on child component

I have a situation which isn't too contrived, and I'm having trouble implementing it using the React best practices. In particular it produces this error:
Uncaught Error: Invariant Violation: setProps(...): You called setProps on a component with a parent. This is an anti-pattern since props will get reactively updated when rendered. Instead, change the owner's render method to pass the correct value as props to the component where it is created.
The situation is like this. The parent contains a child component. The parent has event handlers for UI and for the behavior to work, something inside the child component needs to render its HTML with a CSS change to the height style. Therein lies the wrinkle, usually the information flows upward or stays put, but here I need to change something in the child.
Parent component (Widget) renders this:
<div class="Widget">
<div class="WidgetGrabBar" onMouseDown={this.handleMouseDown}>
<WidgetDetails heightProp={this.props.detailsHeight} />
</div>
And elsewhere in Widget I've got
componentDidMount: function() {
document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.handleMouseUp);
},
componentDidUnmount: function() {
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
},
<...>
handleMouseDown: function(e) {
e.preventDefault();
this.props.actuallyDragging = true;
},
handleMouseUp: function(e) {
this.props.actuallyDragging = false;
},
handleMouseMove: function(e) {
e.preventDefault();
if (this.props.actuallyDragging) {
// update the prop! I need to send an urgent package of information to my child!! jQuery or findDOMElement() followed by DOM traversal is forbidden!!!
this.setProps({
detailsHeight: this.props.detailsHeight + e.deltaY
});
}
},
And I had WidgetDetails' render() render something like:
<div class="WidgetDetails" style={height: this.props.heightProp}>
{detail_items_move_along_nothing_to_see_here}
</div>
I figured that rolling out the jQuery to grab .WidgetDetails to fiddle with its style attr is the wrong thing, the non-React way to go about it. The real anti-pattern.
But now I'm being told that I can't change my props. Or I have to throw out everything including the bathwater in order to have new props. I'm not doing that; my props contain the contents of the detail items. Maybe it is expensive to make another entirely new copy of this.
I'm trying to let React participate in this rendering work to put the new height in. How am I supposed to even do this? Is this error basically enforcing that Props are supposed to be immutable now? The error is telling me that I have to involve this height even farther up on the component chain. I can conceivably do so with a callback from up above, but this feels very wrong. I need to pass information downward, not upward.
Maybe I'm supposed to use state. But changing state forces Widget, the parent component to render. That is not what I desire. Only one singular place in the DOM needs to re-render, that is the child component's div's style attr.
There are two approaches. Either
call handlers on the parent. Then Pass the new props to the child via props. If I recall correctly, that's the approach the react hello world tutorial takes.
Mutate state in the view via setState.
In your case, it seems that approach 2 really makes sense. You are basically trying to track view data.
Never, by the way, update state directly. Use setState. The whole point of reacts virtual dom is that it's optimized for spurious updates, so you will be fine. There is also the life cycle method componentShouldUpdate in case you want finer control.
For completeness I should add that there's a third way of using a global store. That's what react flux adds. But again, in your case that's probably over kill.

Idiomatic way to listen to model changes

I'm playing with React for the first time and I think I really like it. I've implemented (large parts of) the board game Go with it and so far, but I've run into something strange that I don't know how to approach in the idiomatic React way. Basically, I've got a model--the board game--implemented as its own class Board. It exposes only it's constructor, and methods play(i,j) and pass. It handles all of the game logic and updates its own internal state appropriately. It has no reference to anything related to a view/component. I've got a React Component called BoardView which maintains a reference to an instance of a Board. I've also got a Component called AlertView that displays messages about the game state (illegal moves and such) when appropriate.
Everything works well now, and I like the separation of concerns between the Board class and its views. However, the way I have my Board class communicate its changes to the views is unusual, and I feel that it is inconsistent with other React code. Basically, I abuse jQuery's event system to allow me to trigger arbitrary events like ["update", "atari", "suicide"]. In this scheme, the Component has an onClick listener that calls Board.play, which triggers 0 to many events on the Board instance. The Component listens for an "update" event, and calls this.setState, which will force it to re-render(), putting the view into a state that correctly depicts the game. The AlertView listens for the "atari" and "suicide" events on the same board instance and similarly calls this.setState, which triggers another render().
Should I cut out the jQuery events? If so, what's the best way of doing this?
All code is available here and you can play with the app here.
Edit:
For posterity's sake, this question was asked at commit 3f600c.
I'm not sure if this is idiomatic React, but from the React tutorial, the onSubmit handler is passed from the parent to the children as a props.
In your case that would mean to pass the onPlay handler from BoardView to BoardIntersection like this:
var BoardView = React.createClass({
getInitialState: function() {
return {"board": this.props.board}
},
playHandler: function(i, j) {
this.props.board.play(i, j)
},
render: function() {
...
intersections.push(BoardIntersection({
color: this.state.board.board[i][j],
row: i,
col: j,
onPlay: this.playHandler
}));
...
}
})
and BoardIntersection will call onPlay as needed:
var BoardIntersection = React.createClass({
handleClick: function() {
this.props.onPlay(this.props.row, this.props.col);
},
})
tungd's comments pointed me in the right direction, but I decided to answer my own question for a more complete answer.
I ended up removing all of the custom events being fired on the model. I found the following snippet from the React docs to be especially helpful:
A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via props. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way.
Instead of firing events like "atari" and "suicide" on the model, I just set boolean properties on the model in_atari and attempted_suicide. Now, only one "parent" Component in my application has state. It renders all sub-components via declarative props. The AlertView is one such sub-component whose render method now checks the new boolean flags to render the appropriate text. The main parent Component passes a handler to its sub-components that updates the component state (and subsequently forces a re-render).
In the relevant commit, I've named the parent component ContainerView.

Categories