How to fix "this is undefined" in VueJS? - javascript

I've created a new component in VueJS and defined two methods.
One method is action(vuex) and another one is regular method.
actionTypes.js
const TOGGLE_PREVIEW = 'togglePreview';
component
method: {
...mapActions([actionTypes.TOGGLE_PREVIEW]),
onClickPreview: () => {
this.togglePreview();
}
It occurred an error; Uncaught TypeError: Cannot read property 'togglePreview' of undefined.

When a Vue instance is created Vue proxies data, methods, props and injections on the instance for easy access. To proxy methods it uses Function.prototype.bind ..
The bind() method creates a new function that, when called, has its
this keyword set to the provided value
However this doesn't work on arrow functions since their scope cannot be bound and they inherit their parent's scope. So the solution in your case is to use a normal function instead so that it's this scope can be correctly bound to the Vue instance.
methods: {
...mapActions([actionTypes.TOGGLE_PREVIEW]),
onClickPreview() {
this.togglePreview();
}
}

Related

Accessing this inside super() method - javascript

The code is as follows
class ComposerForm extends BaseForm {
constructor(formsObject, options) {
super({
...options,
setup: {},
});
this.formsObject = { ...formsObject };
}
..
}
Now i have a new form
class PreferencesForm extends ComposerForm {
constructor(company, options = {}) {
super(
{
upids: new UpidsForm(company).initialize(),
featureSettings: new FeatureSettingsForm(company)
},
options
);
}
}
When initialising the FeatureSettingsForm, i need to pass the Preference form along with the company object
Something like
{
featureSettings: new FeatureSettingsForm(company, {prefForm: this})
},
so that i can access the preference form inside featureSettings form.
But this cannot be done since this cannot be accessed inside the super method.
Any idea on how to achieve this?
If I understand you right,
You need to pass a FeatureSettingsForm instance in the object you're passing to super (ComposerForm) in the PreferencesForm constructor, and
You need this in order to create the FeatureSettingsForm instance
So you have a circular situation there, to do X you need Y but to do Y you need X.
If that summary is correct, you'll have to¹ change the ComposerForm constructor so that it allows calling it without the FeatureSettingsForm instance, and add a way to provide the FeatureSettingsForm instance later, (by assigning to a property or calling a method) once the constructor has finished, so you can access this.
¹ "...you'll have to..." Okay, technically there's a way around it where you could get this before calling the ComposerForm constructor, by falling back to ES5-level ways of creating "classes" rather than using class syntax. But it general, it's not best practice (FeatureSettingsForm may expect the instance to be fully ready) and there are downsides to have semi-initialized instances (that's why class syntax disallows this), so if you can do the refactoring above instead, that would be better. (If you want to do the ES5 thing anyway, my answer here shows an example of class compared to the near-equivalent ES5 syntax.)

Test method called in mounted hook Vue class component typescript

I am trying to test that a method is called on mount of Vue component. Fairly new to Vue and Typescript.
export default class App extends Vue {
mounted () {
this.deviceId()
this.ipAddress()
this.channel()
this.show()
this.campaign()
this.adUnit()
}
this approach works but I get a warning:
it('mounted methods are called', async () => {
const deviceId = jest.fn()
wrapper = shallowMount(App, {
methods: {
deviceId
}
})
expect(deviceId).toHaveBeenCalled()
})
The error:
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:1735
[vue-test-utils]: overwriting methods via the `methods` property is deprecated and will be removed in the next major version. There is no clear migration path for the `methods` property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex method extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I have tried using jest spyOn, but I cannot find a way to access the method;
const spy = jest.spyOn(App.prototype, 'methodName')
wrapper = shallowMount(App)
expect(spy).toHaveBeenCalled()
Gives the following error:
Cannot spy the deviceId property because it is not a function; undefined given instead
The following also doesn't work:
const spy = jest.spyOn(App.methods, 'methodName')
Error:
Property 'methods' does not exist on type 'VueConstructor<Vue>'.ts(2339)
And the following:
const spy = jest.spyOn(App.prototype.methods, 'deviceId')
Error:
Cannot spyOn on a primitive value; undefined given
I have read in places I may need to define an interface for the component but I am not sure how this works with defining functions inside or if it is necessary?
I've been facing the same issue for a few days, but I've found the way of pointing to the correct method when calling jest.spyOn().
It's a bit tricky but you'll find the methods of your class like this:
const spy = jest.spyOn(App.prototype.constructor.options.methods, 'deviceId');
Note that (even if it might seem obvious, just in case) you'll need to do this before wrapping your component, i.e. like this:
const spy = jest.spyOn(App.prototype.constructor.options.methods, 'deviceId');
wrapper = mount(App, { /* Your options go here */ });
By the way, you don't need to define methods property inside options.
Define your method under the property methods. Only then you can access them from the class.
export default class App extends Vue {
methods: {
deviceId(){
console.log("do your stuff")
}
}
}
See here for more examples for the usage of methods

Variable undefined when used outside of component constructor

I'm working on a project with React and Electron and have an error. I have a component with a constructor that takes in props (which come in the form of two variables.) The constructor is instantiated in a separate file. The issue is that the variable works fine (for instance if do console.log to output it) in the constructor, but outside of it, the variables comes back undefined.
I've already tried using .bind to bind it, but that didn't help and it still turned up as undefined.
This is where the constructor is called:
const dropDown = new Dropdown({
editor,
monaco
});
Here is the constructor and an example of where I am trying to use the variables:
constructor(props) {
super(props);
// Define variables
this.editor = props.editor;
this.monaco = props.monaco;
// Returns correct object
console.log(this.monaco);
// Bind functions
this.changeLanguage = this.changeLanguage.bind(this);
this.filterFunction = this.filterFunction.bind(this);
this.dropDown = this.dropDown.bind(this);
}
changeLanguage(language) {
// Returns undefined all the time
console.log(this.monaco);
this.monaco.editor.setModelLanguage(this.editor, language);
}
I expect the variable to be the same in both the constructor and the functions elsewhere in the file, yet for some reason, it's only defined in the constructor.
You can use this.props.monaco. Or you can pass the props to the changeLanguage function if you rewrite it
changeLanguage(language, props) {
// Returns undefined all the time
console.log(props.monaco);
props.monaco.editor.setModelLanguage(this.editor, language);
}
and call it: this.changeLanguage('English', this.props)

Angular/Typescript: Accessing object data in constructor

Trying to access an object's data in a constructor returns an "undefined" object. It works on the ngOnInit() function, but the data (going to get reset) is needed every time the component starts.
import { Component, OnInit, Input } from '#angular/core';
#Input() data: any;
constructor(dataService: DataService)
{
console.log(this.data); // undefined
}
ngOnInit()
{
console.log(this.data) // works here
}
Have you read this documentation?
It said:
After creating a component/directive by calling its constructor,
Angular calls the lifecycle hook methods in the following sequence at
specific moments
And then it lists all methods according to the order of excecution.
You can read the full documentation of lifecycle hook, it is good to know this when we start developing application using Angular.
You can't access the value of a component input inside its constructor, except for the initial value you assign to it.
Why?
Well, easy: Angular must create the component first (by invoking its constructor). Only then can it set the inputs. If you try to use an input property in the constructor, obviously it will be undefined.
In your case, you should use the value in ngOnInit, or, if you really need it in the constructor, retrieve it in another way (by using an injected service, a global shared object ...).

exported method and unexported methods in Vue.js component

I have two functions defined in a component. foo() is defined just within <script>, and fooExported() is defined in the body of export default {}
My understanding is that functions inside export default {} can be accessed in the template, so it sounds the "unexported" function foo() is a "private" function only available within the <script> scope (Is this correct?). What other difference do they have?
Also I'm trying to access this.$data in the "unexported" method but it shows undefined error. Is it not possible to access the data?
<template>
...
</template>
<script>
function foo(){
console.log(this.$data.message) // error: 'this' is undefined.
}
const bar = 123
export default {
data(){
return {
message: 'MyMessage'
}
},
methods: {
fooExported(){
console.log(this.$data.message) // this works.
}
}
}
</script>
<style scoped>
</style>
You are defining a component in a single-file component .vue file. This means that everything inside the default object is passed directly to the constructor method for a new Vue instance. Vue knows to automatically set the reference to this in any method defined within the methods object.
Your foo method is never handled by Vue, and the reference to this does not point to the Vue instance in the context of that function.
If you want your foo method to have a reference to the message data property, you could call the method from the created hook and pass the this.message as a parameter:
created() {
foo(this.message);
}
side note: as you can see above, you can reference data properties directly from this; you don't need to go through this.$data.

Categories

Resources