How to get component instance in data section in vuejs template? - javascript

I have a component that has complex rendering logic.
I try to carry out this logic to helper classes, for simplifying.
To do this, in the data section (for reactivity), I create class references as follows:
export default {
data: () => ({
state: new InitialState(this),
query: new QueryController(this)
})
}
As I understand it, at this point the context of this is not yet defined.
So, I have two questions.
1) Is there a way to pass the this component context in the data section (without lifecycle hooks)?
2) Is the approach with references to external classes of vuejs philosophy contrary?

Component instance is already available when data function runs, this is one of reasons why it has been forced to be a function.
Due to how lexical this works with arrow functions, it's incorrect to use them to access dynamic this. It should be:
data() {
return {
state: new InitialState(this),
query: new QueryController(this)
};
})
The problem with InitialState(this) is that the entire component instance is passed instead of relevant data, this breaks the principle of least privilege.
Despite Vue isn't focused on OOP, there's nothing wrong with using classes. One of possible pitfalls is that classes may not play well with Vue reactivity because it puts restrictions on the implementation. Another pitfall is that classes cannot be serialized to JSON and back without additional measures, this introduces limitations to how application state can be handled.

As I understand it, at this point the context of this is not yet defined.
Only because of the way you've written the code. The component instance does exist and is available. It is sometimes used to access the values of props for determining the initial values of data properties.
For example, here is an example from the documentation:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
export default {
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
}
The reason why your code doesn't work is because you are using an arrow function. If you change it to the following then this will be available:
export default {
data () {
return {
state: new InitialState(this),
query: new QueryController(this)
}
}
}
See also the note here:
https://v2.vuejs.org/v2/api/#data
Note that if you use an arrow function with the data property, this won’t be the component’s instance, but you can still access the instance as the function’s first argument
As to your other question about whether using classes like this is contrary to Vue...
I don't think the use of classes like this is encouraged but they can be made to work so long as you understand the limitations. If you have a clear understanding of how Vue reactivity works, especially the rewriting of properties, then it is possible to write classes like this and for them to work fine. The key is to ensure that any properties you want to be reactive are exposed as properties of the object so Vue can rewrite them.
If you don't need reactivity on these objects then don't put them in data. You'd be better off just creating properties within the created hook instead so the reactivity system doesn't waste time trying to add reactivity to them. So long as they are properties of the instance they will still be accessible in your templates, there's nothing special about using data from that perspective.

I think computed is a better way to do what you want
export default {
computed:{
state(){
return new InitialState(this);
},
query(){
return new QueryController(this);
}
}
}

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

What is the role and usage of the setup function provided by vue3's Composition API

I need to know the correct usage and the best practice of the setup function provided by vue3's Composition API.
I checked in my current project where developers actually use the setup function instead of creating the component with the traditional approach.
If it is just a design principle or improvement something then where we should apply these. I read the official documentation but instead, they didn't explain the concept, they just provided the list of arguments available in this function.
MyBook.vue
<template>
<span>Warning:- {{warning}}</span>
<button #click="warning = !warning">toggle</button>
</template>
<script>
import { ref } from 'vue'
export default {
props: ['warning'],
setup(props, context) {
const warning = ref(props.warning)
return {
warning,
}
},
}
</script>
<MyBook
:warning="true"
/>
As you can see above, I can't use the same name of a property to data attribute for a component but in the case of setup, we can do this and update the value. (as property should not change within component).
The Vue devtool is also showing the setup as a different category.
setup sets up an instance and returns properties that it should have. The purpose of Composition API, which setup is a part of, is to replace Options API, where an instance is determined by component options. So setup is the replacement for data, methods, computed, watch and lifecycle hooks.
As the reference explains, setup also replaces beforeCreate and created lifecycle hooks, the rest of hooks are set inside of it.
There is no conflict between data and props in setup function because props is accessible as setup parameter, i.e. warning and props.warning are accessible at the same time. In a template, they aren't and shouldn't be distinguished, they instance properties, the solution is to not allow name conflicts. They have been previously available with $data.warning and $props.warning magic keywords but their use wasn't encouraged. If warning value differs from a prop of the same name, and both should be available in a template, it should have a different name.

How to induce reactivity when updating multiple props in an object using VueJS?

I was witnessing some odd behaviour while building my app where a part of the dom wasn't reacting properly to input. The mutations were being registered, the state was changing, but the prop in the DOM wasn't. I noticed that when I went back, edited one new blank line in the html, came back and it was now displaying the new props. But I would have to edit, save, the document then return to also see any new changes to the state.
So the state was being updated, but Vue wasn't reacting to the change. Here's why I think why: https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects
Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive
Sometimes you may want to assign a number of properties to an existing object, for example using Object.assign() or _.extend(). However, new properties added to the object will not trigger changes. In such cases, create a fresh object with properties from both the original object and the mixin object
The Object in my state is an instance of js-libp2p. Periodically whenever the libp2p instance does something I need to update the object in my state. I was doing this by executing a mutation
syncNode(state, libp2p) {
state.p2pNode = libp2p
}
Where libp2p is the current instance of the object I'm trying to get the DOM to react to by changing state.p2pNode. I can't use $set, that is for single value edits, and I think .assign or .extend will not work either as I am trying to replace the entire object tree.
Why is there this limitation and is there a solution for this particular problem?
The only thing needed to reassign a Vuex state item that way is to have declared it beforehand.
It's irrelevant whether that item is an object or any other variable type, even if overwriting the entire value. This is not the same as the reactivity caveat situations where set is required because Vue can't detect an object property mutation, despite the fact that state is an object. This is unnecessary:
Vue.set(state, 'p2pNode', libp2p);
There must be some other problem if there is a component correctly using p2pNode that is not reacting to the reassignment. Confirm that you declared/initialized it in Vuex initial state:
state: {
p2pNode: null // or whatever initialization value makes the most sense
}
Here is a demo for proof. It's likely that the problem is that you haven't used the Vuex value in some reactive way.
I believe your issue is more complex than the basic rules about assignment of new properties. But the first half of this answer addresses the basics rules.
And to answer why Vue has some restrictions about how to correctly assign new properties to a reactive object, it likely has to do with performance and limitations of the language. Theoretically, Vue could constantly traverse its reactive objects searching for new properties, but performance would be probably be terrible.
For what it's worth, Vue 3's new compiler will supposedly able to handle this more easily. Until then, the docs you linked to supply the correct solution (see example below) for most cases.
var app = new Vue({
el: "#app",
data() {
return {
foo: {
person: {
firstName: "Evan"
}
}
};
},
methods: {
syncData() {
// Does not work
// this.foo.occupation = 'coder';
// Does work (foo is already reactive)
this.foo = {
person: {
firstName: "Evan"
},
occupation: 'Coder'
};
// Also works (better when you need to supply a
// bunch of new props but keep the old props too)
// this.foo = Object.assign({}, this.foo, {
// occupation: 'Coder',
// });
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Hello {{foo.person.firstName}} {{foo.occupation}}!
<button #click="syncData">Load new data</button>
</div>
Update: Dan's answer was good - probably better than mine for most cases, since it accounts for Vuex. Given that your code is still not working when you use his solution, I suspect that p2pNode is sometimes mutating itself (Vuex expects all mutations in that object to go through an official commit). Given that it appears to have lifecycle hooks (e.g. libp2p.on('peer:connect'), I would not be surprised if this was the case. You may end up tearing your hair out trying to get perfect reactivity on a node that's quietly mutating itself in the background.
If this is the case, and libp2p provides no libp2p.on('update') hook through which you could inform Vuex of changes, then you might want to implement a sort of basic game state loop and simply tell Vue to recalculate everything every so often after a brief sleep. See https://stackoverflow.com/a/40586872/752916 and https://stackoverflow.com/a/39914235/752916. This is a bit of hack (an informed one, at least), but it might make your life a lot easier in the short run until you sort out this thorny bug, and there should be no flicker.
Just a thought, I don't know anything about libp2p but have you try to declare your variable in the data options that change on the update:
data: {
updated: ''
}
and then assigning it a value :
syncNode(state, libp2p) {
this.updated = state
state.p2pNode = libp2p
}

JSX props should not use .bind() - how to avoid using bind?

I have a container that I need to change the UI form showing the form or showing a success page.
The container has a state.showSuccess and I need the MyFormModule to be able to call the container to change the state.
The below code works but I'm getting the following warning:
JSX props should not use .bind()
How can I get this to work without using .bind()?
...
const myPage = class extends React.Component {
state = { showSuccess: false };
showSuccess() {
this.setState({
showSuccess: true,
});
}
render() {
const { showSuccess } = this.state;
if (showSuccess) {...}
....
<MyFormModule showSuccess={this.showSuccess.bind(this)} />
You should first understand WHY this is a bad practice.
The main reason here, is that .bind is returning a new function reference.
This will happen on each render call, which may lead to a performance hit.
You got 2 options:
Use the constructor to bind your handlers (this will run only once).
constructor(props) {
super(props);
this.showSuccess = this.showSuccess.bind(this);
}
Or create your handlers with arrow functions so they will use the
lexical context for this, hence you won't need to bind them at
all (you will need a babel plugin):
showSuccess = () => {
this.setState({
showSuccess: true,
});
}
You should not use this pattern (as others suggested):
showSuccess={() => this.showSuccess()}
Because this will as well create a new function on each render.
So you may bypass the warning but you are still writing your code in a bad practice design.
From the ESLint docs:
A bind call or arrow function in a JSX prop will create a brand new
function on every single render. This is bad for performance, as it
will result in the garbage collector being invoked way more than is
necessary. It may also cause unnecessary re-renders if a brand new
function is passed as a prop to a component that uses reference
equality check on the prop to determine if it should update.
Use an arrow function when defining showSuccess
showSuccess = () => {
this.setState({
showSuccess: true,
});
}
Use an arrow function since they automatically inherit the this context of wherever they are defined.
showSuccess={() => this.showSuccess()}
Here is a link to the facebook documentation on this subject, which lists this method among others as a solution. Interestingly, they also list using .bind in the prop as one of the solutions, even though it produces a warning when actually used.
From that documentation, you'll note that this is a potential performance issue, since the function will be recreated on every render:
Note:
Using an arrow function in render creates a new function each time the
component renders, which may have performance implications (see
below).
But also from the same link:
Is it OK to use arrow functions in render methods? Generally speaking,
yes, it is OK, and it is often the easiest way to pass parameters to
callback functions.
If you do have performance issues, by all means, optimize!
So I would say if your component will be re-rendering very frequently, you should use one of the other solutions: bind in the constructor, or define the method with an arrow function in the first place. But if not, use whatever method seems cleanest to you.

Find and manipulate a React.js component from the JavaScript console

When I work with JS I tend to whip out a console for the browser and manipulate values on the fly.
I have a page where I use React to render some components and I had the idea that it would be great to be able to manipulate it's state from the console to debug a design quirk which is only visible if the component is in a corner-case state.
I ran into problem that I was unable to get hold of a reference to my component.
I figured there might be a list of active components currently being rendered somewhere, but I was not able to find one on the React global object or anywhere else.
Is there an exposed reference to the components being rendered?
I'm rendering the component like:
<script>React.render(React.createElement(Comp, domElem))</script>
I could store a reference to the result of React.createElement() but it seems to be an antipattern. Also I'm using the ReactJS.NET library to handle server-side rendering for me so the whole React.render line is generated and is hard to modify.
My other idea was to create a mixin that makes the component explicitly expose itself on mount, like:
var ActiveComponents = [];
var debugMixin = {
componentDidMount: function () {
var id = this.getDOMNode().id;
ActiveComponents[id] = {
id: id,
getState: () => { return this.state; },
setState: (state) => { this.setState(state); },
comp: this
};
}
};
Are there drawbacks for an approach like this? Is this the same antipattern mentioned above?
Although being much cleaner than entangling these test hooks in the component code directly, adding a mixin is still a modification, and I would like to avoid that if possible.
The questions I hope to get answers for are bolded.
A workaround for this is to assign your object to the window object:
window.myStateObject = myStateObject
and then you can inspect it in the console:
window.myStateObject
There is a ReactJS extension for Chrome that may meet your needs https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
If that isn't good enough, React keeps track of all the mounted components in a private variable instancesByReactID. If you just want to access these for debugging, you could modify the React code and expose that variable as a global.

Categories

Resources