Member variables in react class are "shared" by reference - javascript

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

Related

Cannot update the state of an object within the state (react)

I have an object variable within the state of my react app. I initialize it with the structure that I want the object to have. Late I am trying to update that object using the setState function. My issue is, I can't get it to actually update anything within the state. The way my code currently looks is:
// the initial state
var initialState = {
object: {
foo: '',
bar: 0,
array: [<an array of objects>]
}
};
// in componentDidMount
this.state = initialState;
// updating the object that is within the state (also in componentDidMount)
let object = {
foo: 'Value',
bar: 90,
array: [<a populated array of objects>]
};
this.setState(prevState => ({
...prevState.object,
...object
}));
console.log(this.state.object); // returns the object defined in initial state
I am really not sure how to fix this issue. I have also been trying a few other methods (especially the ones outlined in this post: Updating an object with setState in React
I have not tried every method outlined in that post but the ones I have been trying have not been working and I figure that this means it is a mistake that does not involve which method I am using. Any insight into this issue would be greatly appreciated. I tried to make this code as concise as possible but if you want to see the exact code I am using (which is pretty much just this but more) just ask.
Edit 2
You have to care each object key equality.
You can do this.
this.setState((prevState) => {
console.log('logging state: ', prevState)
return {
...prevState.object,
object:{ ...object }
})
)}

React : cannot add property 'X', object is not extensible

I am receiving props in my component. I want to add a property 'LegendPosition' with the props in this component. I am unable to do that. Please help me with this.
I have tried this code yet but no success:
var tempProps = this.props;
tempProps.legendPosition = 'right';
Object.preventExtensions(tempProps);
console.log(tempProps);
You can't modify this.props. Here tempProps is reference of this.props so it does not work. You should create a copy of the props using JSON.parse() and JSON.stringify()
var tempProps = JSON.parse(JSON.stringify(this.props));
tempProps.legendPosition = 'right';
Object.preventExtensions(tempProps);
console.log(tempProps);
For a better and efficient way to deep clone object see What is the most efficient way to deep clone an object in JavaScript?
props is not mutable, you cant "add" anything to them. if you want to "copy" them then you need to do
const tempProps = {...this.props};
And the only reason i can see you needing to add more props is to pass it down to a child, but you can do that without adding it to the props object.
EDIT: add props with extra prop
<Component {...this.props} legendPosition="right" />
I want to send the updated props to a child component, If it is possible without copying or cloning to a new object, Please help me how can I achieve this.
Solution is as simple as:
<ChildComponent {...this.props} legendPosition="right" />
Of course legendPosition will be available in ChildComponent by this.props.legendPosition.
Of course earlier this.props can contain already legendPosition property/value which will be overwritten by defined later - order matters.
Of course there can be many spread operators - for multiple properties, logic blocks ... whatever:
const additonalProps = {
legendPosition: 'right',
sthElse: true
}
return (
<ChildComponent {...this.props} {...additonalProps} />
)
below in tempProps object spread operator copy your this.props object and after spread operator we can add new object property or we can update existing object property.
var tempProps = {
...this.props,
tempProps.legendPosition = 'right' //property you want to add in props object
};
Your answer is in this line.
var tempProps = this.props;
this.props is immutable that means you can not change the property value in function.
you can use a this.state so you can modify it in your function
props --- you can not change its value.
states --- you can change its value in your code, but it would be active when a render
happens.
For anyone getting this error with jest, make sure to mock your component this way:
jest.mock('component/lib', () => ({
YourComponent: jest.fn()
}))
Then in your test you can mock the implementation:
(YourComponent as jest.Mock).mockImplementation(() => <div>test</div>);
Hope this works
You can define default values for your props properly by assigning to the special defaultProps property:
https://reactjs.org/docs/typechecking-with-proptypes.html
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// Specifies the default values for props:
Greeting.defaultProps = {
name: 'Stranger',
};

Iterate loop over state object with an array key in React

I have a function getBar() returning an object like:
{
foo: 'value',
array: ['a', 'b', 'c']
}
Here's my React component calling the above function i.e. getBar():
class foo extends Component {
state = {
bar: {}
};
componentDidMount(){
this.setState({
bar : getBar()
});
}
render() {
{this.state.bar.array.map((value, i) => <div class="row" key={i}>{value}</div>)}
}
}
It always gives me Uncaught TypeError: Cannot read property 'map' of undefined error. Exploring similar questions, I came to know I will have to declare an empty state array which I did in different ways but none worked. Can anybody please give me an appropriate answer preferably with complete logic.
I tried another way of declaring the state array to a const in render() but didn't get successful results.
Ok, so this is actually something to do with your component's lifecycle
The problem is that your render method runs before the componentDidMount method. So the first time your component renders your state looks like this:
{
bar: {},
}
So no array property on bar, which means you cannot map over it (which is why you get errors 😣). Instead you could use the componentWillMount method, to set the state before the render method runs, or you could do a check on array being set before mapping over it.

ReactJS: Compare props and state on shouldComponentUpdate

I want to check all properties and state if they are changed, return true if any changed and make a base component for all my root components.
I'm wondering if it won't be the best practice and make my components slow.
Also, what I did always returns true:
shouldComponentUpdate: function(newProps, newState) {
if (newState == this.state && this.props == newProps) {
console.log('false');
return false;
}
console.log('true');
return true;
},
Is there anything wrong with my code?
Should I check for every variable inside props and state?
Won't check for objects inside them make it slow depending on their size?
It is considered best practice to compare props and state in shouldComponentUpdate to determine whether or not you should re-render your component.
As for why it's always evaluating to true, I believe your if statement isn't performing a deep object comparison and is registering your previous and current props and state as different objects.
I don't know why you want to check every field in both objects anyway because React won't even try to re-render the component if the props or state hasn't changed so the very fact the shouldComponentUpdate method was called means something MUST have changed. shouldComponentUpdate is much better implemented to check maybe a few props or state for changes and decide whether to re-render based on that.
I think there's a problem in most of the tutorials I've seen (including the official docs) in the way that stores are accessed. Usually what I see is something like this:
// MyStore.js
var _data = {};
var MyStore = merge(EventEmitter.prototype, {
get: function() {
return _data;
},
...
});
When I used this pattern, I found that the newProps and newState in functions like shouldComponentUpdate always evaluate as equal to this.props and this.state. I think the reason is that the store is returning a direct reference to its mutable _data object.
In my case the problem was solved by returning a copy of _data rather than the object itself, like so:
get: function() {
return JSON.parse(JSON.stringify(_data));
},
So I'd say check your stores and make sure you're not returning any direct references to their private data object.
There is a helper function to do the comparison efficiently.
var shallowCompare = require('react-addons-shallow-compare');
export class SampleComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
render() {
return <div className={this.props.className}>foo</div>;
}
}

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

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.

Categories