Why getters don't have access to state in React components? - javascript

Trying to get access to the component state from a getter, I noticed that this is set to a different context than in a normal method and therefore this.state doesn't work.
See here:
http://jsfiddle.net/tkaby7ks/
Why is that and how can I get access to the state from a getter?

The point is that the getter is a property of the object you pass to React.createClass, not of the class that is created: react treats it as a value. From reacts perspective, the following 2 code snippets are exactly the same:
var MyComponent = React.createClass({
foo: "asdf",
...
})
vs.
var MyComponent = React.createClass({
get foo() { return "asdf" },
...
})
For functions that you pass to createClass, react binds the this variable to the component, but for getters it is not possible.

Related

Vue.js/Vuex: Calling getter vs Accessing state value directly on create lifecycle hook

I'm using the created lifecycle hook in vue.js to load data from my store to the data for a vue component. I noticed that this.selectedType = store.state.selectedType successfully loads the data from the store. However, if I use the getter to load from the store (i.e. this.selectedType = store.getters.getType()), I get the following error:
Error in created hook: "TypeError: Cannot read property 'selectedType' of undefined"
I don't understand why it is saying that selectedType is undefined because selectedType has the value "Item" in the store and is correctly loaded on create if I use this.selectedType = store.state.selectedType.
The getter is defined as such:
getters: {
getSelectedType: state => {
return state.selectedType
}
}
And the state is defined as:
state: {
selectedType: "Item"
}
Could someone please explain why this occurs? I'm hunch is that there is something about the lifecycles that I don't fully understand that is leading to this confusion.
You are not supposed to call getters. Just like computed properties, you instead write it like you are reading a variable. In the background the function you defined in the Vuex store is called with the state, getters (and possibly rootState and rootGetters) and returns some value.
Beside that, it is usually an anti-pattern to use a lifecycle hook to initialise any variable. Local 'component' variables can be initialised in the data property of the component, while things like the vuex state usually end up in a computed property.
The last thing I want to point out is that, if you have correctly added the store to your Vue application, you can access the store in any component with this.$store. To use getters in your application, you can use the mapGetters helper to map getters to component properties. I would recommend using something like this:
import { mapGetters } from 'vuex';
export default {
// Omitted some things here
computed: {
...mapGetters({
selectedType: 'getSelectedType'
})
},
methods: {
doSomething () {
console.log(this.selectedType);
}
}
}
Which is functionally equivalent to:
computed: {
selectedType () {
return this.$store.getters.getSelectedType;
}
}

how mobx knows which variables component is binded to?

I know that MobX can detect when an object property changes (in our case myData.name), but how does MobX know the User component depends on myData.name?
class MyData {
#observable name = "John"
}
#observer
class User extends Component {
render() {
// here, User depends on myData.name, but how MobX know??
let { name } = this.props.myData;
return <div>{name}</div>
}
}
let myData = new MyData();
ReactDOM.render(<User myData={myData} />, document.getElementById('root'));
// this triggers User component to refresh, but how does MobX know User
// component depends on MyData.user?
setTimeout(function(){
myData.name = "Peter";
}, 2000)
The render() function of User is of special importance to mobx becuse you have decorated User as an observer (with #observer). As mobx docs say....
"MobX reacts to any existing observable property that is read during the execution of a tracked function."
"reading" is dereferencing an object's property, which can be done
through "dotting into" it (eg. user.name) or using the bracket
notation (eg. user['name']).
"trackable functions" are the expression of computed, the render()
method of an observer component, and the functions that are passed
as the first param to when, reaction and autorun.
"during" means that only those observables that are being read while
the function is executing are tracked. It doesn't matter whether
these values are used directly or indirectly by the tracked
function.

Assigning a property with ES6 in a React Component

I am new to ES6 and still trying to grasp the concepts of the new specifications, i am currently working on a component in React where i need to make an ajax call and store this response in an object. Then use this object to the map the necessary elements
My component looks like the following
export class App extends Component {
search(){
//make ajax call
response = obj.responseText;
}
getValues(){}
render(){
let result = response.data.map(this.getValues);
return(
<div onKeyDown={this.search.bind(this)}>{result}</div>
)
}
}
How do i declare the "response" variable globally which gets assigned the data from ajax call "obj.responseText"?
It seems like you know what you want to achieve, but are a little confused about how to get there.
I would highly recommend reading the React documentation before you go any further.
Why not global variables?
How do I declare the response variable globally?
In short, don't. Global variables are well-documented as being evil. One instance of this component in a page with a global variable to store its search results would be fine, but imagine if you had two or more instances - they would all share/overwrite each other's search results.
Introducing state
Instead, you want to use React's component state functionality to store your search results.
You can set an initial state by setting a component's this.state in its constructor, (or in ES5, define a getInitialState method on the component).
Then, any time you want to update the component's state, you can call its this.setState(...) method, passing in a new state object. This will also trigger a re-render of the component.
Example
Here is a simple implementation following the above pattern:
export class App extends Component {
// Set the initial state of the component in the constructor
constructor(props) {
super(props);
this.state = {};
}
// This gets called when your component is mounted
componentDidMount() {
// Here we make our AJAX call. I'll leave that up to you
performMyAjaxMethodDefinedSomewhereElse(result => {
// We call this method to update `this.state` and trigger re-rendering
this.setState({ result });
});
}
render() {
// If we haven't received any results yet, display a message
if (!this.state.result) {
return (
<div>No results!</div>
);
}
// Iterate over the results and show them in a list
const result = this.state.result.map(text => (<li>{text}</li>));
// Display the result
return (
<ul>{result}</ul>
);
}
}
Naturally, if you don't want the AJAX call to fire off immediately, you can use a very similar approach, replacing componentDidMount with an event handler which looks almost identical.

in a react component, how to get `this` in static function?

attempting to create a static function within a react component. the function uses this to get its data, but this is out of scope when the function is called.
here is a very simple example:
var Test = React.createClass({
val: 5,
statics: {
getVal: function() { return this.val }
},
render: return( <div>{this.val}</div> )
});
Test.getVal(); => undefined!!
obviously this has lost its scope when Test.getVal() is called. how to get this inside the getVal() function?
fyi, the following standard javascript parent approach does not work:
Test.getVal.apply( Test ); => undefined
Check out the docs on statics.
Whatever you put in statics is not going to have the context of an actual React component instance, but the val property you're defining is a property of an actual React component instance. It's not going to exist before you actually render the component, because that's when all the non-static properties are constructed. Statics are supposed to be component-related functions that are usable outside the context of an actual instance, just like for example static functions in C# and many other languages.
It simply doesn't seem to make sense to want to access a React component instance from a statics function. Maybe you need to think over what you're actually trying to achieve. If you really want to be able to access a specific component's properties, then I guess you can pass the instance as an argument to the static function, but then of course that would be usable once you have actually constructed a component.
Ahh ok misunderstanding. If you need to somehow be able to call this method whenever then your val must be located in statics as well but your render function would then have to reference Test.val instead of this.val. If this isn't a requirement though it would be best to stick to props/state and accessing things from within the component since the component will not autoupdate based on changes to the val.
var Test = React.createClass({
statics: {
val: 5,
getVal: function() {
return this.val
}
},
render: function(){
return( <div>{Test.val}</div> )
}
});
console.log('VAL IS' , Test.getVal());
Link to fiddle with example https://jsfiddle.net/dgoks3Lo/

Member variables in react class are "shared" by reference

When i create several instances of a react class (by using React.createElement on the same class), some member variables are shared between the instances (arrays and objects are shared, strings and booleans etc. not).
For me this feels horrible and scary and wrong. Is this a bug or is there another way to do what i want to do?
Please have a look:
http://jsbin.com/kanayiguxu/1/edit?html,js,console,output
What you should be doing is setting state on your component, instead of having state as arbitrary properties on your React component.
So instead of doing this:
var MyComponent = React.createClass({
myArray: [1, 2, 3],
componentWillMount() {
this.myArray.push(this.myArray.length + 1);
},
render() {
return (
<span>{this.myArray.length}</span>
);
}
});
You should be doing this:
var MyComponent = React.createClass({
getInitialState() {
return {
myArray: [1, 2, 3]
};
},
componentWillMount() {
this.setState(state => {
state.myArray.push(state.myArray.length + 1);
return state;
});
},
render() {
return (
<span>{this.myArray.length}</span>
);
}
});
The reason being that all of a components state and data should reside in this.state and this.props which is controlled and handled by React.
The benefit you get from using props and state for this, is that React will know when those change, and from that it can tell when it's time to re-render your component. If you store state as arbitrary properties or globals, React won't know when those change, and cannot re-render for you.
The reason for the behaviour you're seeing is that every instance of the component uses the object you give to React.createClass() as its prototype. So all instances of the component has a myArray property, but that is on the prototype chain, and thus shared by all instances.
If you truly want something like this and you want to avoid this.state, you should use something like componentWillMount and inside that method, assign properties to this. This will make sure that such data is only on that particular instance, and not on the prototype chain.
EDIT
To even further clearify, it can be good to know that the object passed to React.createClass() isn't the actual object on the prototype. What React does is that it iterates over all properties on that object, and copies them onto the prototype of the React element object. This can be illustrated by this example:
var obj = {
myArray: [1, 2, 3],
title: 'My title',
componentWillMount() {
this.myArray.push(this.myArray.length + 1);
},
render() {
return (
<span>{this.myArray.length}</span>
);
}
}
var MyComponent = React.createClass(obj);
// This doesn't change the component, since 'obj' isn't used anymore
// by React, it has already copied all properties.
obj.title = 'New title';
// This however affects the component, because the reference to the array
// was copied to the component prototype, and any changes to what the
// reference points to will affect everyone who has access to it.
obj.myArray.push(666);

Categories