React documentation states that
shallowCompare returns true if the shallow comparison for props or
state fails and therefore the component should update.
So, if is understand correctly, if there is no state in my component and i know there are no changes in the props keys, this code
let shallowDiff = Object.keys(this.props).filter((item) => {
return this.props[item] !== nextProps[item];
});
return shallowDiff.length !== 0;
should return the same as the react comparison. But it doesn't. If there are no changes, my code correctly returns an empty array, whereas react returns true. I am trying to understand this behavior and searching a way to search the problem-key, but i just do not get it.
shallowCompare is a legacy add-on. Use React.PureComponent instead.
https://facebook.github.io/react/docs/shallow-compare.html
If there are no changes in state or props, shallowCompare returns false. And of course, when there are changes, shallowCompare returns true, and proceeds with the re-render of the Component.
But if you have a deeply nested object, shallowCompare will not be able to tell that the nested objects have updated/changed.
You either write your own function to check if the object has changed, or you can use a very naive method to check for changes if the ORDER of the properties is NOT important.
JSON.stringify(obj1) === JSON.stringify(obj2)
I personally don't recommend using the shallowCompare or React.PureComponent because the use case is too narrow. If you have a complex object, neither function is effective. If your function works for you, then use it.
Related
I've been reading React's Quick Start documentation;
Whether you declare a component as a function or a class, it must never modify its own props
This is a "pure" function, because it doesn't attempt to change its inputs, and always returns the same result for the same inputs:
function sum(a, b) {
return a + b;
}
This in an "impure" function, because it changes its own input:
https://codesandbox.io/s/9z38xv4x7r
function SayHi(props) {
props.name = "Jim"; // TypeError Cannot assign to read only property 'name' of object '#<Object>'
return <h1>Hi {props.name}!</h1>;
}
Why are React props read-only?
A component should manage its own state, but it should not manage its own props. props is essentially "state that is managed by the component owner." That's why props are immutable.
React docs also recommends to treat state as if it's immutable.
That is because by manipulating this.state directly you are circumventing React’s state management, which can be potentially dangerous as calling setState() afterwards may replace the mutation you made.
You may think of React component as a function of its props and state. As you advance through the docs, you'll find that this is the case, as most functions in the React component life cycle have signatures of the form (prop, state) => { //code }.
React docs define props as any arbitrary input given to a component, and the component will render something based on the props ( and sometimes based on state too, if it is a stateful component ). So props is like something that is given to the component for say, reference. Imagine it this way: you are a component, and your parent component gives you a reference book, containing some rules on how you must behave ( a.k.a. render ). Two cases may arise:
You are dumb (stateless): You just read the book, and behave so.
You are smart (stateful): You read the book, and then note some things in your notepad, that you may view, update or delete. You may even take copy down content from the book to your notepad, and then edit the notepad.
Either way, you may not update the reference book given to you. Only the parent component can update it ( example, give you another book, or change its content ).
I don't know if this is a correct representation, but React components work in a similar way. You'll get the hang of it soon. Make sure you read Thinking in React too. Happy coding!
The props of a react component is aimed to store values and functions from its parent component. It's just the pattern, props are immutable. If you want to have a variable that would be mutable, then store it in the state of the component. States are mutable.
Why does the object returned by the useRef hook stores whatever value it's supposed to hold in current property? Why can't we assign something directly into the ref object as shown below:
const sampleRef = useRef([]);
/** why can't we do this... */
sampleRef.push('1');
/** ...instead of this? Why an extra `current` object? */
sampleRef.current.pus('1');
What is the purpose of useRef returning the argument wrapped inside another object with current property?
The answer to this question is not specific to React and its hooks system.
Mutating an object is just a solution to how you share values between different closures / scopes.
When you call useRef() for your component a "ref object" is created in React internals, tied to that particular instance of your component.
Each time useRef() is called across multiple renders, the same "ref object" is returned. Setting current on it is how your store a value to re-access it on next render.
By doing something like
let value = useRef();
value = 1234;
you're throwing away the ref object, replacing it with a new value in your local scope. There's no way React can track that action and update the ref object that is stored in its internals. (In fact React does not track any action to "ref objects" anyway, it just gives them to you. You mutate them, you access them).
But with the current API, you do
const ref = useRef(); // ref is an object that is stored somewhere in React internals
ref.current = 1234; // you update the property of that object
The next time your component renders, React gives you the same ref object, so that you can use the value that you set previously.
As I understood, they made it because they needed to create an object in order to seal the DOM element object(in development mode) and memoize it. As you know if we are going to memoize something, we need to convert it to an object or array.
Reference:
function mountRef<T>(initialValue: T): {current: T} {
const hook = mountWorkInProgressHook();
const ref = {current: initialValue};
if (__DEV__) {
Object.seal(ref);
}
hook.memoizedState = ref;
return ref;
}
https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.js#L916
The react hooks system works with immutable values. Whenever the component is rendered, the hooks are called (for example the useState hook), and they produce a value or two (state and setter function). If this values are changed from the previous values, other hooks might be called (useEffect when the setter function is initialised).
However, sometimes we don't want to react to this changes. We don't really care what is the value as long as it's there, and we don't care if something changes it. For this cases we've got the ref:
The “ref” object is a generic container whose current property is
mutable and can hold any value, similar to an instance property on a
class.
Whenever you need to store a value, that will be used, but won't cause a re-render, nor cause useMemo, useCallback, useEffect, etc... to recompute, you can set that value via a ref. Since the ref itself will be used as part of hooks dependencies (useMemo(() => {}, [ref]), you can't update it. To enable immutability, a property inside that ref object, can be changed ref.current, without causing the dependant to recompute, since it's the same ref.
I'm trying to answer the root cause of your question, which I interpret to be something like: "It seems that we should not need an intermediate object with a .current property." You're correct. We don't. Whatever the actual reason regarding useRef, I have noticed it is possible to instead do something like the following, which has the kind of syntax you were asking for in your question (the elimination of .current):
//note foo is in array brackets by itself
const [foo /*no setFoo here*/]= useState({bar:"baz"});
...
foo.bar="hello"
or
//note foo in brackets by itself
const [foo /*no setFoo here*/]= useState([]);
...
foo.push(1);
This lets us directly mutate foo's properties without using .current. As long as we never call setFoo, mutating foo's properties will not cause a rerender on its own. The value of foo itself is never changed, since it always points to the same object or array.
However, it is possible, just as with any other variables including useRef ones, to cause a useEffect hook to rerun after a rerender if a changed property like foo.bar appears in the 2nd argument array of the useEffect.
I haven't tried let [foo]= useState("whatever") yet. In that case, we'd be altering the actual value of foo, and depending on React to give that altered value back to us on subsequent rerenders, even though we never notified React of the change. Seems sketchy.
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.
Does Redux Saga select return a mutable or immutable state?
See https://github.com/redux-saga/redux-saga
The select operator just uses the Redux store's getState() method internally. So, the return value from the selector function is probably going to correspond to the contents of the store state tree.
Redux itself does not actually stop you from mutating your state. It wants you to handle state immutably, but the base library does not enforce that in any way.
As a result, if your store state tree is composed of plain JS objects, then yes, the return value from select is almost definitely going to be mutable. If your store state tree use made up of something like Immutable.js Maps and Lists, then the return value from select will probably be immutable.
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.