TypeError: <...> is not a function - javascript

So, I'm running into a problem and I'm not sure exactly how to resolve it. After reading through the ES6 doc's I think I have this set up correctly, yet when I call <UserInstance>.getID() I get the error:
TypeError: currentUser.getID is not a function.
I know this may be a duplicate but in the other questions I've seen answer similar questions, none of them have allowed me to resolve this issue.
Here's my class definition:
import { v4String } from "uuid/interfaces";
class User {
private id!: v4String;
constructor() {
this.getID = this.getID.bind(this);
this.setID = this.setID.bind(this);
}
getID = () => this.id;
setID = (id: v4String) => this.id = id;
}
export default User;
I'm pretty sure I have the class set up, but is there something I'm missing with the arrow function? It doesn't seem to matter if I set it up with the arrow function syntax, or set it up like
getID() {
return this.id
}
Here's the code that's calling it, currentUser is provided by a context provider and injected into the props using a Higher Order Component:
componentDidMount() {
const currentUser: User = this.props.currentUser;
this.setState({ loading: true });
const currentUserID = currentUser.getID(); <---- FAILS HERE
const currentUserIDString = currentUserID.toString();
}
}

TypeError: currentUser.getID is not a function.
This error means that currentUser is some value which does not have a getID method on it. Your class is fine, so something is wrong with the value of currentUser and not the User class.
It appears that currentUser is a plain javascript object, and not an instance of your class.
const currentUser: User = this.props.currentUser;
This line does not make currentUser an instance of User, it merely a type hint for typescript. And it is a type hint that is incorrect.
Somewhere (where is up to you) you need to call new User() in order to be able to use the methods that you have defined on your user class. If you never call new User() then you do not have an instance of User, you just have a plain object.

Related

Using instance as VueJS data

First of all, sorry for my bad english.
My question is: Can I use an instance as data in the VueJS?
Take a look at these two ways of update the User's name.
const vue = new Vue({
data(){
return {
user: {
name: 'Denis Wilton',
},
}
},
methods: {
updateUserName(newName) {
this.user.name = newName;
}
}
})
<button #click="updateUserName('Foo Bar')">Change name to Foo Bar</button>
In the above way, I can create an event that calls the method "updateUserName('Foo Bar')" passing the newName as param.
Ok! But... What if I create an user instance from the User class, that by yourself carries the method updateName, like this:
//#/classes/User.js
//Class User
class User {
constructor(name) {
this.name = name;
}
updateName(newName) {
this.name = newName;
}
}
//////////////////////////////
const vue = new Vue({
data(){
return {
user: new User('Denis Wilton'), //Here I instantiated my user class, that owns the 'updateUser' method implicitly.
}
}
});
In the above example, I can use an event that calls the User's method "updateName" for updating my data, and have no need to code the method at VueJS component's script.
<button #click="user.updateName('Foo Bar')">Change name to Foo Bar</button>
Am I sinning to do this? LOL
The best response to this was on a reddit thread: https://www.reddit.com/r/vuejs/comments/gqx58s/anyone_using_oop_principles_in_their_vuejs_spa/
Summary: It might mess with the built in reactivity of vue and give you problems down the line if you use OOP principles in Vue.

Cannot access object properties from dynamic import. Vue.js + ts

My customer wants to have other instances of the app with another content as well (so called 'themes'). We decided to use env variable + prefilled object with content. So, this content may exist or it may not.
I created a function that conditionally imports module with all the content and it actually even working:
theme: string;
hasAnyTheme: boolean;
static getThemedMiscContent(): {[key: string]: string} {
let miscContent = {};
if (hasAnyTheme) {
import(`#/themes/customers-themes/${theme}/ThemedContent`)
.then(themedContent => {
miscContent = Object.assign(miscContent, themedContent.ThemedMiscContent);
});
}
return miscContent;
}
But when I call it from the component, I can't actually read properties of the object while I can read the object itself.
// computed
miscThemedContent(): {[key: string]: string} {
return ThemeUtil.getThemedMiscContent();
}
// template
{{ miscThemedContent }} // is totally fine
{{ miscThemedContent.someValue }} // undefined
And as the weird fact, this issue appears in only one of my 3 components that use that function. Others work just fine.
As far, as I understood, that kind of error appears when Vue tries to use that value before the object is loaded. So, I tried to add additional loading variable and nothing is happening. Is there any way to fix that?
Since import is an async function, miscContent could be returned before the import function's execution is completed. I suggest you make use of async/await syntax to wait for the actual result before returning the miscContent, it should be something like this:
static async getThemedMiscContent(): {[key: string]: string} {
let miscContent = {};
if (hasAnyTheme) {
const importTheme = await import(`#/themes/customers-themes/${theme}/ThemedContent`);
if (importTheme && importTheme.ThemedMiscContent) {
miscContent = Object.assign(miscContent, importTheme.ThemedMiscContent);
}
}
return miscContent;
}

Vuex Getter Undefined

I am new to Vue.js and experiencing an issue with Vuex modules and Axios. I have a "post" component that retrieves a slug from the router and fetches data with Axios which is then retrieved with Vuex Getters.
I am able to retrieve data successfully but then I still see this error on my DevTools, "TypeError: Cannot read property 'name' of undefined"
Due to this error I am not able to pass this.post.name to Vue-Meta.
Codes
Post.vue
computed: {
...mapGetters(["post"]),
},
mounted() {
const slug = this.$route.params.slug;
this.fetchPost({ slug: slug });
},
methods: {
...mapActions(["fetchPost"]),
/store/modules/post.js
const state = {
post: [],
};
const getters = {
post: (state) => {
return post;
}
};
const actions = {
async fetchPost({ commit }, arg) {
try {
await axios.get("/post/" + arg.slug).then((response) => {
commit("setPost", response.data);
});
} catch (error) {
console.log(error);
}
},
};
const mutations = {
setPost: (state, post) => (state.post = post),
};
export default {
state,
getters,
actions,
mutations,
};
Your getter is utterly wrong: a state getter is supposed to be a function that takes in the entire state as a param and retrieves whatever you're interested in from it. Your version...
const getters = {
post: (state) => {
return post;
}
};
...takes in the state as a param but doesn't use it. Instead, it returns a variable (post) which has not been defined in that context.
Which will always return undefined, regardless of current value of state.post.
And, as you already know, JavaScript can't access property 'name' of undefined.
To get the current value of state.post, use:
const getters = {
post: state => state.post
}
Or
const getters = {
post: (state) => { return state.post; }
}
... if you fancy brackets.
Also, out of principle, I suggest initializing your post with an empty object {} instead of an empty array [].
Changing variable types as few times as possible is a very good coding habit, providing huge benefits in the long run.
Edit (after [mcve])
You have a bigger problem: the import from your axios plugin returns undefined. So you can't call get on it. Because you wrapped that call into a try/catch block, you don't get to see the error but the endpoint is never called.
I don't know where you picked that plugin syntax from but it's clearly not exporting axios. Replacing the import with import axios from 'axios' works as expected.
Another advice would be to namespace your store module. That's going to become useful when you'll have more than one module and you'll want to specifically reference a particular mutation/action on a specific module. You'll need to slightly change mapActions and mapGetters at that point.
See it working here.

TypeError: Cannot read property 'attributes' of undefined

class CompTable extends React.Component{
constructor(props){
super(props);
this.state = {
products: [],
attributes: [],
attDesc: [],
};
this.getEntries = this.getEntries.bind(this);
}
getEntries = async () => {
const response = await fetch('/api/hello/data');
const body = response.json();
return body;
};
componentDidMount(){
this.getEntries()
.then((resolve) => this.setState({
products: resolve.products,
attributes: resolve.attributes,
attDesc: resolve.attributesDescription}))
.catch(err=>console.log(err));
};
render(){
let obj = this.state.products[1].attributes;
console.log(obj);
return(
<div id = "comp">
<CompHeading comp={this.state.products}/>
</div>
);
}
}
export default CompTable;
The line let obj = this.state.products.attributes returns the mentioned error. What's bizzare is that if I remove the ".attributes", the console logs the product object, with the "attributes" property inside it. It seemed like the object just disappeared when I try to access its property XD. Anyone knows the reason why?
Another strange thing is when i remove the ".attributes" the console of my browser logs six objects (though i only call console.log once) - 4 of the objects show undefined while the two are the correct product[1] object.
Don't trust a console.log, it's not accurate. its value can change. To fix that you can use JSON.stringify on the object you are logging to show its real value when you are logging it.
For your problem, you could do something like that :
if (!this.state.products[1]?.attributes) return <Loader />
else
return (... your content...);
This way you will render a Loader while you data are not available.
You are trying to access this.state.products[1].attributes even before it is populated.
It gets populated from componentDidMount which will run after render function is done execution.
We need to change the render function variable declaration to this -:
let obj = this.state.products.length > 0 && this.state.products[1].attributes;
console.log(obj);
So that way it will get value as undefined in the first pass. And in the second pass when setState gets called from componentDidMount it will get the correct value and your code won't break midway.

Vue.js directive v-html not updating if the model is overwritten

By running the following code (a Vue.js component), I expect that, after the AJAX call returns, both the v-html directive and the console.log() display the same value.
On the contrary, v-html is stuck with "loading...(1)" even though obj.html has a different value, as console.log() confirms.
The behaviour is caused by getObject overwriting obj, and being afterwards obj.html undefined for a short time before getHTML returns (all this happens in function created).
Can please someone explain whether this is Vue's desired behavior (doc links are welcome), or whether should I submit a bug report, or finally whether I am simply structuring my code in a bad way?
Thanks in advance
<template>
<main v-html="obj.html || 'loading... (1)'">
</main>
</template>
<script>
export default {
name: 'Post',
data: function () {
return {
obj: {
html: 'loading... (2)'
}
}
},
created: async function () {
this.obj = await this.getObject()
this.obj.html = await this.getHtml()
console.log(this.obj.html)
},
methods: {
getObject: async function () {
const resp = await this.$http.get('https://jsonplaceholder.typicode.com/todos')
return resp.body[0]
},
getHtml: async function () {
const resp = await this.$http.get('https://jsonplaceholder.typicode.com/todos')
return resp.body[0].title
},
}
}
</script>
The function getObject returns a String so at the first line of created hook
this.obj = await this.getObject()
you change the reference of the obj and you make it pointing to a string and then you try to put a property on a string, which does not work ;)
it's like you would do
this.obj = 'test'
then console.log(this.obj);
// test
and then this.obj.abc = 'whatever'
console.log(this.obj.abc);
// undefined
You would need to parse the object before, see JSON.parse(string)
Update:
If this is not the case i.e you somehow have an object coming from that service.
Then the only problem I can think is that you lose the reference of your original obj and v-html is still pointing to the old one. In that case you have to avoid modification of the root obj or you can use the vue $set method: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
It seems vue data objects are not deeply reactive, which means that altering a property will not trigger change detection in the template.
Try rearranging the created hook to compose the full object before assigning it to the data property. That way when the template reacts it will see the html property of obj.
Ref CodeSandbox
created: async function () {
const fetchedObj = await this.getObject()
fetchedObj.html = await this.getHtml()
this.obj = fetchedObj;
console.log(this.obj.html)
},

Categories

Resources