Why we shouldn't modify the state directly in ReactJS? - javascript

Documentation says
For example, this will not re-render a component:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
But, there is no answer for the Why? What is the justification for using the second one is correct?

When you use such a high level framework, like React, they don't bother explaining to such detail why because it's far too complicated for a simple article. Understanding why would require a deep understanding of React and how the vanilla JavaScript works under the hood. Looking at source code is an option for you, but life is easier when you take their docs at face value.
The virtual DOM:
React keeps a copy of the previous state of the page. It uses it as a reference point when it decides on what should be repainted and what shouldn't. When you click on a button, the entire page doesn't need to repaint the entire DOM to values that are completely identical, but what's kind of shitty about JavaScript is the fact that
Equality by value does not exist for objects in JavaScript
Finding differences between the virtual DOM and the next DOM that React wants to repaint is impossible because JavaScript has no ability to discern
console.log([] === [])
My example doesn't explain the weakness of manually mutating state. It's this one.
this.state = {}
this.state.arr = []
const prevArr = this.state.arr
this.state.arr.push(10)
console.log(this.state.arr === prevArr)
An array of [10] with a new value is registered as equal to [] because equality is done by reference, and adding a value to an existing element is still equal to its previous state. Here is how to fix it. It is no coincidence that you do this in React as well
this.state = {}
this.state.arr = []
const prevArr = [...this.state.arr]
prevArr.push(10)
this.state.arr
console.log(this.state.arr === prevArr)
Making a new copy of the array retains all the pointers to the values, but it is its own distinct entity in your hardware's memory. Now they are different. When React traverses its virtual DOM, it now has the ability to register that you inserted 10 into your array and want that to be reflected in the next iteration of the DOM.
this.setState triggers a rerender
A rerender is not a complete repainting of the DOM. It is triggering a repaint of the particular element you're passing in when it finds a difference.

Related

How to set a variable in React/TS

Part 2 of wanting to absolutely die while learning React/TS.
So I'm in some code that looks like this.
Basically I've tried in 3 different ways to set a variable.
Closure. It doesn't work. This is really a remarkable feat of engineering to break JS basics like this. Literally every single line of code is encapsulated in this React.FC function and that doesn't seem to matter.
useRef. It seems to work but why do I need this to set a variable.
useState. Pretty sure this is for the DOM and not simple flags.
So here it is. 20k hours of Javascript under my belt and I cannot set a variable in React/TS.
const MyElement: React.FC = () => {
// This one suffers from scope issues, somehow.
let currentRow1: HTMLElement | null;
// I guess this works but God does it feel wrong
const currentRow2 = React.useRef<null | HTMLElement>(null);
// Pretty sure this is for DOM rendering anyway
const [currentRow3, setCurrentRow3] = React.useState<null | HTMLElement>(null)
// Here's a handler for an event
const handleClickEvent = () => {
// Ok closure works here..? huh
unsetCurrentRow1()
currentRow1 = somelement
// This works, but why would I want to do this
currentRow2.current = somelement
// At least I'm pretty sure this is not what this is for
setCurrentRow3(somelement)
}
const unsetCurrentRow = () => {
if(currentRow1) // Undefined!!!
currentRow1 = null;
currentRow2.current = null; // Yes
// Again, most likely wrong altogether
setCurrentRow3(null)
}
}
So I feel like I'm taking crazy pills. Do I not understand TS or do I not understand React? Why doesn't closure work? Why am I forced to use an object that has a property called current? What the heck am I doing?
"useState. Pretty sure this is for the DOM and not simple flags."
You're definitely wrong on this. It's for any small or large bit of data that you want to persist between renders.
And in general you want to avoid directly referencing DOM elements if you can at all avoid it. You probably don't need to for the vast majority of simple things.
In a React functional component, the entire function is executed anytime a re-render happens.
That fact has some consequences. The most important of which is that any variable declaration in your component is redeclared in a brand new scope on each render.
That means if you do:
let someVar = 0;
And have an event handler that runs something like:
function onWhatever() {
someVar = someVar + 1;
}
Then when the component re-renders, let someVar = 0 is executed, ignoring any previous value because it's not in scope. Then a new event handler function is created based on that new variable.
So all you are doing is updating the someVar in your local scope, that will get completely discarded when the component re-renders.
This is all a very good thing. It allows you treat variables you need for this render differently from values that need to persist.
State is something that persists on the component. Then your functional component rendering function can ask React "What is the state for this component?" and map the result to a local variable used for that render execution.
You want state for almost any value that should be tracked and updated between renders. And the cool thing is that when state updates, any components that depend on that state are automatically re-rendered. This is very important so that React can know when rendering is required.
It's important to work this way because you may have many of this component on your page, react keeps the state in the component tree to know which component has which state even through many re-renders when the function scope is re-created and re-executed.
Another thing that's mixed up in this is refs. Refs are a container for any value. The primary use case is to grab a reference to a rendered element in order to do something with it.
For example:
const myInputFieldRef = useRef<HTMLInputElement>(null)
return (
<div>
<input ref={myInputFieldRef} />
<button onClick={() => alert(myInputFieldRef.current.value)}>Submit</button>
</div>
)
By using a ref we can access that element directly in callbacks and interact with it imperatively. The reason refs are wrapped in an object with a current property is so that the ref may be passed around and set just once, but what it's referencing may change. This way you can pass the ref once, change its value a hundred times, and whenever you ask the ref for its value it will give the current value.
You can also create a self managed ref as you have done, which has its uses. But you probably want state instead unless you know for a fact that won't work for some reason.
You've confused these two concepts.
It's hard to tell what you are trying to do, but I think want to want is a useState that tracks the current row, as an index or id (number), and that's it. You can then use that change your rendering based on what's current.
Something like:
const rows = ['a', 'b', 'c']
function FooComponent() {
const [currentRow, setCurrentRow] = React.useState<number | null>(null)
return (
<div>
{rows.map((name, index) => (
<div
key={name}
onClick={() => setCurrentRow(index)}
style={{ backgroundColor: currentRow === index ? 'red' : 'white' }}
>
Row {name}
</div>
))}
</div>
)
}
In this example, you click rows to make them selected and turn red.

How does React's functional setState determine which fields have been updated?

From my experimentation, if you have an object as react state (let [state, setState] = useState({})), and you do something like
setState(s => {
s.a = 42;
return s;
})
It will not re-render components that depend on s.a.
If on the other hand you do
setState(s => {...s, a: 42});
It will re-render all components that depend on any field of s.
So it seems to me like it really only looks at whether the returned object reference of the closure is the same as the state it already has or not and makes a binary choice whether to re-render everything or nothing.
Is that correct?
Is there any way to update state in a way that makes it only re-render things that depend on e.g. s.a?
CONTEXT, if it helps: I need this for my application because performance is becoming impractical. My application retrieves JSON information from and API endpoint, which contains a list of 'fields' that describe input fields that the user can use to input data. When the user is done, the application submits this data in a single json. So all input components are controlled, through a single functional state that holds one property per field (I need to be able to programmatically update some fields sometimes). Performance is prohibitive because all fields (quite many now) are updated/re-rendered every time the user types a character in one of them. I sadly cannot create a new state for each field because the amount of fields is not known in advance.
As a note before I begin, this is not okay, and does not work in general. Treat all React state/props as immutable, otherwise you'll have issues.
setState(s => {
s.a = 42;
return s;
})
There is no way to selectively re-render your component, the reason why that mutation on setState doesn't re-render children that depend on s.a is because your component doesn't re-render at all when you update like this because the reference of s doesn't change, so react doesn't see that there's a change.
The only way to make children not re-render when parents re-render is to use React.memo, PureComponent, or shouldComponentUpdate. And those have to be applied to the children rather than the parent.
When does React re-render child component?

Updating an object in the ngrx/store

I'm using #ngrx/store for an Angular 2 app.
My store holds a list of say, Book objects. I want to update a field in one of those objects. I also happen to have an Observable of the Book instance I'm looking to update (say, selectedBook).
To do the update I intend on calling the reducer with an UpdateBookAction, and a payload of the new Book. So I make a deep copy of the existing Book object by subscribing to selectedBook and then calling Object.assign().
But when I try to write to one of the fields of the copy I get the following error. (It happens to be the same error I get if I were to try to write directly to the Book object in the store.)
Error
Cannot assign to read only property 'name' of object '#<Object>' at ViewWrappedError.BaseError [as constructor]
Code
ngOnInit() {
this.book$ = this.store.let(fromRoot.getSelectedBook);
//...
}
someFunction() {
//...
this.book$.subscribe(book => {
let updatedBook = Object.assign({}, book);
updatedBook.name = 'something else'; // <--- THIS IS WHAT THROWS
let action = new BookUpdateAction(updatedBook);
this.store.dispatch(action);
}
}
Clarification after Comments
I was under the assumption that I could have an action with a payload that was not the entire state of the store. (In fact that seems necessary, no?) I'm confident that this is the case given the documentation.
The action I'm looking to take is something like this:
Action = UPDATE, payload = {'id': 1234, 'name': 'something new'}
As mentioned, I intend on making that call like this:
this.store.dispatch(action);
Presumably under the hood, ngrx is passing my action to the reducer along with the (immutable) current state.
So from there, everything should work okay. My logic inside the reducer doesn't mutate the existing state, it simply creates a new one out of the existing state and the payload I've passed in.
The real question here is how I can reasonably build the new "objectToUpdate" such that I can pass that in as the payload.
I could do something like this:
this.book$.subscribe(book => {
let updatedBook = new Book();
updatedBook.id = book.id;
//set all other fields manually...
updatedBook.name = 'something else';
let action = new BookUpdateAction(updatedBook);
this.store.dispatch(action);
}
But we're not just talking about two fields here... what if my book has several fields? Do I have to manually build from scratch a new Book each time just to update one field?
My solution was to do a deep copy using Object.assign({}, book) (and not mutate the old one!) and subsequently make the update to solely the field I was looking to touch.
The idea of the ngrx store is to have one and only one single place of truth, which means all the objects are immutable, and the only way to change anything is to recreate everything as a whole. Also, you are probably using the ngrx freeze (https://github.com/codewareio/ngrx-store-freeze) which means that all of the objects will be created read-only so you wont be able to change any (This is good for development if you want to completely follow the redux pattern). If you remove the part where the store freezes the object, you will be able to change it, but thats not best practice.
What I would suggest you is the following: Use the ngrx observable with async pipe to put the data (in your case books) in a dumb component which can only get input and output some event. Than, inside of the dumb component you can "edit" that object by making a copy of it, and after you are done, you can emit back the changes to the smart component which is subscribed to the store and allow it to change the state via the store (commit). This way is best because it is not very common to change the whole state for a really small change (like two way binding, when user types..).
If you follow the redux pattern, than you will be able to add history, which means the store will keep a copies of the last X state recreations, so you can get UNDO functionality, easier to debug, timeline etc
Your problem is that you are directly editing the property instead of recreating the whole state.
I'll have to make an assumption about the actual scenario the OP is experiencing.
The problem
It's not possible to modify a member of a frozen object. Its the error being thrown.
The cause
ngrx-store-freeze is used as a meta-reducer to freeze any object that enters the store. On another place, when an object needs to be changed, a shallow copy is being made. Object.assign() doesn't do deep copy. A member of another object reached from the original object is being modified. This secondary object is also frozen, by it is not duplicated.
Solution
Use a deep copy like cloneDeep() from lodash. Or sent a bag of properties to be changed with a proper action. Process the changes on the reducer.
As already mentioned - the reason you are getting
Cannot assign to read only property 'name' of object
is because 'ngrx-store-freeze' freezes the state and prevents mutating it.
Object.assign will provide a new object as you expect, but it will copy the state's properties along with each property's own definition - such as the 'writable' definition (which 'ngrx-store-freeze' likely sets to false).
A different approach is described in this answer and explains how cloning objects with JSON.parse(JSON.stringify(yourObject)) as fastest, but this approach has flaws if you keep dates or methods etc' in your state.
using lodash's 'cloneDeep' is probably your best bet for deep cloning the state.
One way to accomplish this is a utility/helper method to make a new book from.
You could give it an existing book and the subset of properties you want to add to a new book (using Partial in typeScript if you want type safety).
createNewBook(oldBook: Book, newProps: Partial<Book>): Book {
const newBook = new Book();
for(const prop in oldBook) {
if(newProps[prop]) {
newBook[prop]=newProps[prop];
} else {
newBook[prop]=oldBook[prop];
}
}
return newBook
}
You could call it via newBook = createNewBook(new Book(), {title: 'first foo, then bar'});
and use this newBook to update your store.

React Native receving new props, componentWillReceiveProps

I'm struggling with some updates on my Component. I know I shouldn't set props inside the states. However, I had to do this to make my component update properly:
componentWillReceiveProps(nextProps) {
this.setState({
doctor: nextProps.data.name,
address: nextProps.data.address
})
}
Is there a better way to do that? Is a best approach if I do this ->
componentWillReceiveProps(nextProps) {
this.props.data.name = nextProps.data.name;
this.props.data.name = nextProps.data.address;
})
}
I was trying to use the shouldComponentUpdate:
shouldComponentUpdate: function(nextProps, nextState) {
return nextProps.id !== this.props.id;
}
but I didn't work quite well for me.c
In your comment, you mention:
when I come back to the ListView the item I unfavourited still there
and another item disappeared
This looks like a problem in a completely different area. My guess would be that you are using the wrong key in a list. Probably an index. Keys should always be unique to the item you are displaying, and index is not (e.g. the first item is always index 0, and when you rearrange the list, or delete the first item, another item will have index 0, and react does not work well then.) Further explanation here.
About "updating the component properly":
If you pass in new props, react automatically re-renders with the new props. You do not need to do anything in componentWillReceiveProps for this.
componentWillReceiveProps is for updating state, based on comparing OLD and NEW props. (e.g. if you want to display whether number of likes in a prop has gone up or down)
shouldComponentUpdate is optional. Main purpose is to increase performance without any functional change to the workings of your component: to tell react early on that the component is unchanged. I would advise not to include shouldComponentUpdate as long as your component does not yet work as intended.
A wild guess here. As I perceive it, you experience issues properly implementing shouldComponentUpdate. I believe you try comparing nextProps.data objects and always have not equal result, even though the data within is equal. The reason for it is that the object references to data objects are different. In order to overcome that issue, you should be doing a deep comparison, similar to lodash's _.isEqual instead.
As mentioned in comments, updating nextProps in componentWillReceiveProps is a horrendous idea.

Best practice for ReactJS form components

I am looking for a best practice to have a ReactJS component responsible for the form for users to edit a given entity. Very simplified example here. Actual forms would in many cases have several more fields and more GUI functionality.
React.createClass({
getInitialState: function() {
return {
entity: {
property1: null,
property2: null
}
};
},
handleChange: function(e) {
var entity = this.state.entity;
switch(e.target.name) {
case 'property1':
entity.property1 = e.target.value;
break;
case 'property2':
entity.property2 = e.target.value;
break;
}
this.setState({
entity: entity
});
},
render: function() {
return (
<div className="entity-form">
<form onChange={this.handleChange}>
<input type="text" name="property1" value={this.state.entity.property1} />
<br />
<textarea name="property2" value={this.state.entity.property2}></textarea>
<br />
</form>
</div>
);
}
});
The fields of the form is directly editing an entity object, that could then be saved to a RESTful api. I want the component to be updated as the user change the fields, so the GUI could react based on the input during typing (like validations, info etc).
In theory, I could have the whole state object represent the entity that is being edited, so every property of the entity is first level state variables. However, I want to be able to add additional state variables for GUI functions and other things related to what the component is going to do, so I would prefer the entity object to be one state variable like the "entity" state variable above. The object could of course be some more complicated object, like a Backbone model or similar, but in this simplified example, I just use a simple object whit the required properties.
So, in search of the best practice way to make React components for this purpose, I have some questions:
Props or state.
In this case, I have chosen to put the entity object with the content for the form in a state variable instead of prop. This is to be able to update the object during form input without having to call the parent and update the props. As far as my React experience goes, that would be the best practice for a form component like this.
Controlled or uncontrolled inputs.
In the simplified example above, I use controlled inputs. This leads to updating the state and re-rendering the component on every change (like every character entered of a text field). Is this the best practice? The good thing is that the component has full control of what happens, instead of having defaultValue paramters, and on some event (like the user pressing a save button), the component extract the values, update the entity and save it to the server. Is there any reasons (or opinions) on if controlled or uncontrolled inputs should be used in cases like this?
onChange for the form or every input
The example has an onChange on the form tag, and it causes the handleChange method to be called every time any of the fields in the form is changed. However, since the inputs are controlled (have value parameters), React complains that the input fields does not have an onChange property. Does this mean having a common onChange on the form tag is bad practice, and I should remove it and put onChange on every single field instead?
Updating individual properties
In the above example, I use a switch based on what input field is being update (when handleChange is called). I guess I could instead make sure all field names is in sync with the property names of the entity, and I can set properties of the entity object in handleChange based on the name of the field from the event (e.target.name). However, this makes it hard to have individual needs per field, even if most fields just update an entity property directly. I guess an alternativ is a switch with a default block setting based on the name of the input, and case blocks for any field that require other ways of updating (like filtering the value before setting it on the entity). Please comment this if you know some much better way of handeling field updates this way.
Updating the state entity
One big problem of this example, is the way the entity object is updated. Since the entity variable in the handleChange is set to the entity object from current state, this is just a pointer, and updating the entity variable will change the object in state. The React pages say you should never update state directly. One of the reasons is something I have experienced when updating the state this way before calling setState. If having a shouldComponentUpdate method, the prevState contain the new state, since the content of the prevState argument sent to the shouldComponentUpdate is based on what was in the state when setState was called. As far as I know, there is no simple way to clone a object in javascript. So the question is, when having whole objects that I need to update properties of (and not touching the other values in the object) instead of just running setState of a single state variable, what is the best way to do this without causing theese kinds of state mixups?
Anything that is going to change goes in State.
If you're looking at loading an existing entity and editing it, you want controlled inputs, and you want to set the values accordingly. I tend to stay away from defaultValue in most cases (outside of dropdowns)
This ties back in to your previous question. If you specify a value, you are using a controlled input, and you have to provide an onChange handler for any controlled input, otherwise it is set in stone. A benefit of controlled inputs is that users can't edit them, so if you had some properties locked down (maybe for read only, security reasons), when the user attempts to save, even if they edited the HTML directly, React should pull the property's value from the vDOM representation (could be wrong here, but I believe I've tested this before). Anyway, you have to have onChange set directly on controlled inputs. As far as using event delgation (at the form level), this isn't a bad practice at all for a lot of events, this is just a specific scenario (controlled inputs) where you need onChange events specified for each element.
Not entirely sure what the ask on this one is, but I used refs instead of target.name.
So, you're correct in that you should never alter the state directly, and this is a tricky bit from the docs. React is going to alter state directly, it's just going to do it in the implementation through setState. If you alter state outside of this method call, unexpected things will happen and errors will be thrown.
shouldComponentUpdate only does shallow comparisons, but there are a few solutions here.
One is to stringify the objects, this is a quick and dirty object comparison, don't really recommend it, but it works.
A better solution, and one I have used with React + Flux is to implement a propertyChanged bool, and just check that in your shouldComponentUpdate.
Now, this will require you to be aware of setting it when things change, i.e., you changed something deeper in the object graph. Say propertyOne is an object with a property that gets changed in your handleChange method. You would validate the input however you wish, then set propertyChanged = true, and you then need to implement componentDidUpdate. We're making an assumption here, but if the component has updated, you set propertyChanged back to false so you don't have any further triggering of unwanted updates. I hope that makes sense. It's kinda like a one-way notifyPropertyChanged.
I'm providing a quick example of what I would probably do for a more dynamic implementation that allows you to add more properties to your object (only shallow properties in this implementation, obviously you could write a more robust solution). Let me know if you have any further questions or if I didn't answer something.
http://jsfiddle.net/rpv9trhh/
var e = {
prop1: 'test',
prop2: 'wee',
prop3: 'another property',
propFour: 'Oh yeah!'
};
var FormComp = React.createClass({
getInitialState: function(){
return {
entity: this.props.entity
}
},
render: function() {
var ent = this.state.entity;
var that = this;
var inputs = [];
for(var key in ent){
inputs.push(<input
key={key}
style={{display:'block'}}
type="text"
ref={key} onChange={that._propertyChanged.bind(null, key)}
value={ent[key]} />)
}
return <form>
{inputs}
<input
type="button"
onClick={this._saveChanges}
value="Save Changes" />
</form>;
},
_propertyChanged: function(propName) {
var nextProp = this.refs[propName].getDOMNode().value;
var nextEntity = this.state.entity;
nextEntity[propName] = nextProp;
this.setState({
entity: nextEntity
});
},
_saveChanges: function() {
var updatedEntity = this.state.entity;
for(var key in updatedEntity){
alert(updatedEntity[key]);
}
//TODO: Call to service to save the entity, i.e.
ActionCreators.saveEntity(updatedEntity);
}
});
React.renderComponent(<FormComp entity={e} />, document.body);

Categories