Binding no-ops or null handlers to Vue event handlers? - javascript

Is there a Vue convention or best practice for defining empty event handlers?
Vue's transition element includes the convenient, though easily overlooked, appear attribute. It uses the transition's enter hooks by default, but a separate set of appear hooks also becomes available. I have a case where :appear should do nothing while :enter triggers a function. If :appear is not bound, Vue falls back to :enter, so something has to be there.
Writing conditional logic into the enter hook feels wrong, but I'm not sure how best to define the hook. The following all work, but if there isn't a Vue convention, which should be considered a best practice?
Boolean attribute
<transition
appear
v-on:appear
v-on:enter="doEnter"
>
"Empty" attribute
<transition
appear
v-on:appear=""
v-on:enter="doEnter"
>
Explicit no-op handler
<transition
appear
v-on:appear="doAppear"
v-on:enter="doEnter"
>
new Vue({
//...
methods: {
//...
doAppear: () => {}
}
});
Some other options could include inline no-op functions v-on:appear="() => {}" or abusing JavaScript's "everything is a function" nature with an empty object v-on:appear="{}" or number v-on:appear="0". false doesn't work, because Booleans aren't functions and Function doesn't work because Vue sees a string instead of the bare prototype.

I don't think there is much of a noticeable difference unless you benchmark it.
If this matters to you, you could choose to not have the appear handler by passing a dynamic object to v-on instead.
<transition ... v-on="transitionEventHandlers">
JS
data: {
shouldUseAppearHandler: true
},
computed: {
transitionEventHandlers() {
let handlers = {
enter: this.doEnter
};
if(this.shouldUseAppearHandler) {
handlers = {
appear: this.doAppear,
...handlers
}
}
return handlers;
}
},
methods: {
doEnter() {
console.log("enter");
},
doAppear() {
console.log("appear");
}
}
example: https://codepen.io/jacobgoh101/pen/aGVzjR?editors=1011

Related

Overriding onClick in React

This is somewhat of weird question. I'm working with event types in React, and we want to use onClick in some instances, and onPointerDownCapture in others (for reasons). But to make this more general, it could be any two different click-like events. The issue is that while we can assign whatever function handler on the right side of the expression, the left side has to be static, essentially. So,
<button
onClick={handler} vs onPointerDownCapture={handler} vs onMouseDown={handler}
/>
I think just using onPointerDownCapture will be fine for most usecases, but in a perfect world, I'd be able to flip between these at runtime based on other variables. Is it possible to override the onClick on the button/div/whatever prototype or something to be whatever event type I want it to be?
Much googling. No success.
I didn’t fully understand what you mean by “overriding onClick”, but
The issue is that while we can assign whatever function handler on the right side of the expression, the left side has to be static, essentially.
This is not true, left hand side could be dynamic, here’s how:
<button {...({ [eventName]: handler })} />
I guess this solves your problem.
Ok above syntax is a bit terse and admittedly confusing. It’s the good old JSX spread props syntax, just over an inline object literal.
I’ll give you another equivalent form, hopefully it should be more readable.
const eventName = someCondition ? "onPointerDownCapture" : "onClick"
const props = {
[eventName]: handler
}
<button {...props} />
You have to use those attribute names and you use the same function name for all 3 of them.
What these 3 attributes do is they register the associated event.
Maybe you could use a useEffect and add there conditionally an event listener instead of the proposed React attributes.
I think best is #vera solution in comment. Pass extra prop to component (for example isOnClick), and based on it pass either callback or undefined to event handler prop:
function Component(props: { isOnClick: boolean; callback: () => void }) {
return (
<div
onClick={props.isOnClick ? props.callback : undefined}
onMouseDown={props.isOnClick ? undefined : props.callback}
/>
);
}
Note that passing undefined to prop is same as not setting that prop.
Alternatively conditionaly return component:
function Component(props: { isOnClick: boolean; callback: () => void }) {
if (props.isOnClick) {
return <div onClick={props.callback}/>
} else {
return <div onMouseDown={props.callback}/>
};
}

React TypeScript callback reading undefined when using setState, but it will log the value

I am writing a react application, and for some reason I can not wrap my head around this issue. Below is a snippet, the only thing that should be needed:
onFirstNameChange(event: any){
console.log(event.target.value)
// this.setState({
// firstname: event.target.value
// })
}
The commented out code will not run, it says it can not read properties of undefined. However when I log the events value it does it perfectly. Any ideas on why this is happening? It is an onchange event. It is also deeply nested, however the value does make it back.
React components written in an ES6 class, do not autobind this to the component's methods. There are 2 solutions primarily. You may use choose either:
Either explicitly bind this in constructor
constructor(props) {
super(props);
// rest of code //
this.state = {
firstname: '',
};
// rest of code //
this.onFirstNameChange = this.onFirstNameChange.bind(this);
}
Or use ES6 Arrow Function
onFirstNameChange = (event: any) => {
console.log(event.target.value);
this.setState({
firstname: event.target.value
});
}
As Brian stated, I also believe the error saying that it cannot read property of undefined, is likely saying it cannot read property setState of undefined, because "this" is undefined.
This is most likely caused by providing the onFirstNameChange handler without leveraging a closure, bind, or arrow function to bind the value of this to the "this" value you are expecting.
My guess is your code leveraging the on change handler looks like the following:
<input type="text" value={this.state.value} onChange={this.onFirstNameChange} />
You can refer to the "Handling Events" page (link here) of the React documentation, you will find the following about midway down the article:
You have to be careful about the meaning of this in JSX callbacks. In
JavaScript, class methods are not bound by default. If you forget to
bind this.handleClick and pass it to onClick, this will be undefined
when the function is actually called.
This is not React-specific behavior; it is a part of how functions
work in JavaScript. Generally, if you refer to a method without ()
after it, such as onClick={this.handleClick}, you should bind that
method.
If calling bind annoys you, there are two ways you can get around
this. If you are using the experimental public class fields syntax,
you can use class fields to correctly bind callbacks.
SOLUTIONS: I've included examples of the 3 possible solutions below to bind the value of this, depending on your preference:
Option 1 - Assuming this is a class based component, which I am based on the syntax shown, you can bind the method in the constructor:
constructor(props) {
super(props);
this.onFirstNameChange.bind(this);
}
Option 2 - Update method definition to public class fields syntax with an arrow function to bind "this": (See here)
onFirstNameChange = (event: any) => {
console.log(event.target.value)
this.setState({
firstname: event.target.value
})
};
Option 3 - Update onChange callback with anonymous arrow function to bind this value:
<input type="text" value={this.state.value} onChange={() => this.onFirstNameChange} />
Note on option 3 from React Docs:
The problem with this syntax is that a different callback is created
each time the LoggingButton renders. In most cases, this is fine.
However, if this callback is passed as a prop to lower components,
those components might do an extra re-rendering. We generally
recommend binding in the constructor or using the class fields syntax,
to avoid this sort of performance problem.

Vuejs - Issue when removing a component via directives and the mounted/created event is being executed

I wanted my directive to work as v-if since in my directive I have to check access rights and destroy the element if it does not have access.
Here is my code
Vue.directive('access', {
inserted: function(el, binding, vnode){
//check access
if(hasAccess){
vnode.elm.parentElement.removeChild(vnode.elm);
}
},
});
vue file
<my-component v-access='{param: 'param'}'>
The issue is that i'm applying this directive to a component, it's removing the component but not the execution of functions called by the created/mounted hook.
In the component(my-component) there are functions in mounted/created hook. The execution of these functions are done and I don't want these functions to be executed. Is there a way to stop execution of the mounted/created events?
It is impossible to replicate the behavior of v-if in a custom directive. Directives cannot control how vnodes are rendered, they only have an effect on the DOM element it is attached to. (v-if is special, it's not actually a directive but instead generates conditional rendering code when the template is compiled.)
Though I would avoid doing any of the following suggestions if possible, I'll provide them anyway since it's close to what you want to do.
1. Extend the Vue prototype to add a global method
You definitely need to use v-if to do the conditional rendering. So all we have to do is come up with a global helper method which calculates the access permission.
Vue.prototype.$access = function (param) {
// Calculate access however you need to
// ("this" is the component instance you are calling the function on)
return ...
}
Now in your templates you can do this:
<my-component v-if="$access({ param: 'param' })">
2. Define global method in the root component
This is basically the same as #1 except instead of polluting the Vue prototype with garbage, you define the method only on the root instance:
new Vue({
el: '#app',
render: h => h(App),
methods: {
access(param) {
return ...
}
}
})
Now in your templates you can do this:
<my-component v-if="$root.access({ param: 'param' })">
Now it's clearer where the method is defined.
3. Use a global mixin
This may not be ideal, but for what it's worth you can investigate the viability of a global mixin.
4. Use a custom component
You can create a custom component (ideally functional but it needn't be) that can calculate access for specific regions in your template:
Vue.component('access', {
functional: true,
props: ['param'],
render(h, ctx) {
// Calculate access using props as input
const access = calculateAccess(ctx.props.param)
// Pass the access to the default scoped slot
return ctx.scopedSlots.default(access)
}
})
In your templates you can do this:
<access :param="param" v-slot="access">
<!-- You can use `access` anywhere in this section -->
<div>
<my-component v-if="access"></my-component>
</div>
</access>
Since <access> is a functional component, it won't actually render it's own component instance. Think of it more like a function than a component.
A bit overkill for your situation, but interesting nonetheless if you ever have a more complicated scenario.

Event never gets emitted in VueJS within an axios call

I have a component which needs to initialize data at the beginning, it does some transformations to the data (based on some values which are saved in localstorage to mark those data as selected).
Using Vue 2.5.
// component.vue
import Vue from 'vue'
export default Vue.extend({
data() {
fetchedData: [],
},
mounted() {
this.getStuff()
window.Bus.$on('localStorageRefreshed', this.getStuff)
},
computed: {
selectedData() {
return this.fetchedData.filter(data => data.selected)
}
},
methods: {
getStuff() {
const doTransformations = function (res, existing) {
// blabla
}
axios.get('/endpoint/for/stuff/').then(result => {
doTransformations(result, this.fetchedData) // not exactly what happens, but I think this is unneeded to solve my problem. mostly there to illustrate how this all fits together.
window.Bus.$emit('stuffFetched', this.selectedData)
})
},
}
})
So window.Bus is a Vue instance which is just posing as a global event handler. Within my getStuff-method, the event never gets emitted, and I have no idea why. I've console logged everywhere in order to figure out if the bus is just not initialized (it is, because I have tons of components which works perfectly with it). The event is just never emitted. I've tried wrapping the emitting in $nextTick but that doesn't work either (I also tried doing this directly in the mounted-lifecycle method, because the computed property updates in Vue devtools like it should and contains all the right stuff).
I'm really at a loss of what to do here, everything seems to work like it should, but the event is not even registered in Vue devtools.
The reason I need to fire this event is in order to do some price calculations for another component which exists outside of the scope of this child and it's parent. Just emitting in the child scope (this.$emit('dataChanged')) doesn't emit an event either using this approach.
Anyone have an idea of what my ******* brain is doing to me here?
Did you try to use async await?
That i'll probably make the timeout job, something like:
async mounted() {
await this.getStuff()
window.Bus.$on('localStorageRefreshed', this.getStuff)
}
and then do that on your getStuff too:
async getStuff() {
const doTransformations = function (res, existing) {
// blabla
}
await axios.get('/endpoint/for/stuff/').then(result => {
doTransformations(result, this.fetchedData) // not exactly what happens,
but I think this is unneeded to solve my problem. mostly there to
illustrate how this all fits together.
window.Bus.$emit('stuffFetched', this.selectedData)
})
}

vue, emitting vs passing function as props

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).

Categories