Using instance as VueJS data - javascript

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.

Related

TypeError: <...> is not a function

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.

React setState for nested object while preserving class type

Extending from this question React Set State For Nested Object
The way to update nested state is to decompose the object and re-construct it like the following:
this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
However, this is a problem for me, as it will not preserve the component class (More detail below). I am aiming for my state to be structured like the following:
this.state = {
addressComponent: {
street: '',
number: '',
country: ''
},
weather: {
/* some other state*/
}
}
To make my life easier, I created a simple address class that builds the object for me and have some other utility function such as validation.
class AddressComponent {
constructor(street, number, country) {
this.street = street;
this.number = number;
this.country = country;
}
validate() {
if (this.street && this.number, this.country) {
return true;
}
return false;
}
}
This allows me to do is change the initialization of state to be:
this.state = {
addressComponent : new AddressComponent(),
weather: new WeatherComponent();
}
this will allow my view component to perform nice things like
if (this.state.addressComponent.validate()) {
// send this state to parent container
}
The problem with this approach is that if I want to mutate one single piece of information like country and use the above Stack Overflow approach such as:
this.setState({addressComponent: {...addressComponent, country: 'bluh'}})
Doing this will mean that the resolting addressComponent is no longer part of AddressComponent class hence no validate() function
To get around it I cound recreate a new AddressComponent class every time like:
this.setState({addressComponent: new AddressComponent(this.state.street, this.state.number, 'bluh');
But this seems weird.
Am I doing this wrong? Is there a better approach? Is it acceptable to use classes like this with react?
It's undesirable to use anything but plain objects for React state to avoid situations like this one. Using class instances will also make serialization and deserialization of the state much more complicated.
The existence of AddressComponent isn't justified, it doesn't benefit from being a class.
The same code could be rewritten as functional with plain objects:
const validateAddress = address => !!(street && address.number && address.country);
...
if (validateAddress(this.state.address)) {
// send this state to parent container
}
I think what you said about reinstantiating your class each time you update it in your state is the cleanest way to ensure your validate() method is called at that time. If it were me, I would probably write it as:
const { addressComponent } = this.state;
this.setState({ addressComponent: new AddressComponent(addressComponent.street, addressComponent.number, 'bluh') });
You can create a reusable function to update a complex state like this.
updateState = (option, value) => {
this.setState(prevState => ({
addressComponent: {
...prevState.addressComponent,
[option]: value
}
})
}

VueJS observe plugged parameter

I'm starting with VueJS 2 and I created a simple plugin which adds parameter to Vue instance.
I have problem because when I update this value my computed properties are still same.
My example plugin's code:
export default function (Vue) {
Vue.MyProperty = "test"
Object.defineProperties(Vue.prototype, {
"$myProperty": {
"get": function () {
return Vue.MyProperty
},
"set": function (value) {
Vue.MyProperty = value
return this
}
}
})
}
And my component's code
export default {
"computed": {
"test": function () {
return this.$myProperty
}
}
}
When I changed this.$myProperty in other component my component returns vaid value (in example when I changed from "test" into "newvalue" I can see "newvalue") but computed property test is still old value ("test" in my example).
I tried to use this.$set(this, "$myProperty", value) but this still not working.
How can I use or declare this property to use it in computed or watched properties?
The reason the data value is not automatically updated in the computed is because the property you added to Vue, MyProperty is not an observed property. Fundamentally, Vue's reactivity works because all values added to data are converted into observed properties; under the hood they are converted into getter/setter pairs with some additional code so that when one of those properties changes, Vue knows to propagate the changes to all the things that depend on it's value.
The code in the question, however, just adds a normal property to the Vue object. You can change it, but it's not reactive.
That said, it's relatively easy to make it reactive. I cover how to do this in the comments to my answer here. Basically, instead of adding your property to Vue, just create a new Vue object (which has very low overhead) and make the property you want to be reactive a property of that Vue. Here is a working example.
console.clear()
function MyPlugin(Vue) {
let store = new Vue({data:{MyProperty: "some value"}})
Object.defineProperties(Vue.prototype, {
"$myProperty": {
"get": function () {
return store.MyProperty
},
"set": function (value) {
store.MyProperty = value
return this
}
}
})
}
Vue.use(MyPlugin)
const MyComponent = {
template:`<div>{{test}}</div>`,
"computed": {
"test": function () {
return this.$myProperty
}
}
}
new Vue({
el: "#app",
components:{
MyComponent
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
<my-component></my-component>
<button #click="$myProperty = 'new value'">Change</button>
</div>

Accessing VUE JS's data from Axios

I have a Vue JS (Vuetify) App that makes an ajax request that I would like to populate a div's content with the response, however I am having difficulties accessing the instance's data. All examples I have seen use this to point to the data object, but when I do I get this error
Unable to set property 'message' of undefined or null reference
The app is quite simple:
main.js:
import Vue from 'vue'
import App from './App.vue'
import Vuetify from 'vuetify'
Vue.use(Vuetify)
new Vue({
el: '#app',
render: h => h(App)
})
App.vue
export default {
data () {
return {
....
message: '',
order: {},
...
},
methods: {
send: function() {
axios.post(this.api+"orders",this.order).then(function(response) {
this.message = "Your payment was successful";
...
}
}
}
this.order is accessible without a problem with Axios' post method however the anonymous function that handles the promise returned seems to have a problem accessing this.message, in contrary to the examples I have seen.
What is it that I am doing differently here?
I can think of these solutions for your problem.
1) You can create a reference to this and use it.
send: function() {
let self = this
axios.post(this.api + "orders", this.order).then(function(response) {
self.message = "Your payment was successful"
}
}
2) An arrow function will enable you to use this which will point to your Vue instance.
send: function() {
axios.post(this.api + "orders", this.order).then(response => {
this.message = "Your payment was successful"
}
}
3) Use bind to assign an object to this which will be the current Vue instance in your case.
send: function() {
axios.post(this.api + "orders", this.order).then(function(response) {
this.message = "Your payment was successful"
}.bind(this))
}
Your problem is this line
axios.post(this.api+"orders",this.order).then(function(respo‌​nse) {
Examples may use this as you say however, by using a second level of nested function expression, you are accessing a different dynamic this than you think you are.
Basically, send is the method of the Vue object, but since this is not lexically scoped inside of function expressions, only inside of => functions, you have the wrong this reference in the callback you are passing to Promise.prototype.then.
Here is a breakdown:
methods: {
send: function() {
// here: `this` technically refers to the `methods` object
// but Vue lifts it to the entire view object at runtime
axios.post(this.api + "orders", this.order)
.then(function(response) {
// here: `this` refers to the whatever object `the function is called on
// if it is called as a method or bound explicitly using Function.prototype.bind
// the Promise instance will not call it on anything
// nor bind it to anything so `this` will be undefined
// since you are in a module and modules are implicitly strict mode code.
this.message = "Your payment was successful";
});
}
}
Try this instead
export default {
data() {
return {
message: "",
order: {},
},
methods: {
send: function() {
// here: `this` technically refers to the `methods` object
// but Vue lifts it to the entire view object at runtime
axios.post(this.api + "orders", this.order).then(response => {
// here: this refers to the same object as it does in `send` because
// `=>` functions capture their outer `this` reference statically.
this.message = "Your payment was successful";
});
}
}
}
or better yet
export default {
data() {
return {
message: "",
order: {},
},
methods: {
async send() {
const response = await axios.post(`${this.api}orders`, this.order);
this.message = "Your payment was successful";
}
}
}
Note in the second example, which uses JavaScript's recently standardized async/await functionality, we have abstracted away the need for a callback entirely so the point becomes moot.
I suggest it here, not because it relates to your question, but rather because it should be the preferred way of writing Promise driven code if you have it available which you do based on your use of other language features. It leads to clearer code when using Promises.
The key point of this answer however, is the scoping of the this reference.

How do I add a static method to my TypeScript class?

I am trying to define an API using TypeScript such that it can work like this:
// Create new user (Working)
var user : IUser = new Api.User({ firstName: "John", lastName: "Smith" });
// Delete existing user (Working)
Api.User.Delete(1);
// Load existing user (Unsure how to implement)
var user = Api.User(123);
My TypeScript:
module Api
{
export class User
{
constructor(id : number)
constructor(user : IUser)
constructor(user? : any)
{
// ...
}
static Delete(id : number) {}
}
}
I am not sure how to have a static method Api.User() i.e. not use new. I don't know what to call this type of construct, which makes it difficult to research. :(
I did try adding an unnamed static to the User class, but this isn't right.
static (id : number)
{
// ...
}
Option 1: export a function directly on the Api module
You can export a function on the Api module to retrieve/create a User instance:
module Api
{
export class User
{
}
export function GetUser(id: number):User {
return new User();
}
// or a slightly different syntax (which generates different JavaScript):
export var Delete = (id: number) => {
};
}
You can't have the class named User and the function also be User, so I've changed it to GetUser in the example.
You could then call:
Api.GetUser(1234)
or
Api.Delete(1234);
Option 2: Using an interface
You could also approach this by using an interface if you wanted to limit the ability of
calling code from being able to instantiate instances of the inner class by using an interface instead. Below I've created a simple ISuperUser interface and and implementation of the class called SuperUserImpl. As the SuperUserImpl isn't exported, it's not publicly creatable. This is nice in that you could use simple Api.SuperUser(2345) to return new instances of a class that implements the ISuperUser interface.
module Api {
export interface ISuperUser {
id: Number;
name: String;
}
class SuperUserImpl implements ISuperUser
{
constructor(public id: Number) {
}
public name: String;
}
export var SuperUser = (id:Number):ISuperUser => {
return new SuperUserImpl(id);
}
}
var su : Api.ISuperUser = Api.SuperUser(5432);
alert(su.id);
Option 3: JavaScript and instanceof
There's a trick that is often used in JavaScript class constructor wherein the function/constructor for a new object checks to see whether it is of the right type (was the function called or created), and if not created, returns a new instance:
if (!(this instanceof User)) {
return new User(id);
}
While correct, TypeScript when trying to call a constructor with that will cause a compiler warning. This will work, but with the compiler warning:
constructor(id: Number) {
if (!(this instanceof User)) {
return new User(id);
}
}
And later calling:
var u: Api.User = Api.User(543);
A compiler warning suggests, "did you forget to use 'new'?" It does produce valid JavaScript, just with a warning. I'd probably go with the static-like method approach to avoid the TypeScript compiler warning.

Categories

Resources