I am trying to figure out the most idiomatic implementation of the react lifecycle method shouldComponentUpdate. I feel that I, and possibly others, don't utilize this method to the extent it could be because it is optional.
Generally I want to check if if the props or state of an object has changed between updates.
This does not work as this equality is pointing at the object reference:
shouldComponentUpdate(nextProps, nextState) {
return this.props !== nextProps;
}
So then we go down the rabbit hole of object cloning, which seems like a little bit of an ugly solution:
return JSON.parse(JSON.stringify(this.props) !== JSON.parse(JSON.stringify(nextProps));
// lodash cloning method
return _.cloneDeep(this.props) !== _.cloneDeep(nextProps);
Another possibility is using an immutable library like immutablejs, but that is another dependency I'm not sure I want to add to the project, and another API to learn.
Am I missing something? Is there a more concise approach to this?
You can simply use React's shallow compare to compare props and states.
Hope this helps!
Related
I'm trying to understand when it is necessary to use a custom equality function in useSelector().
I have a selector that returns a simple object as:
const data = useSelector(state => state.data) // => { a: "foo", b: "bar }
I though that since data is an object, I needed to use a custom equality function to avoid unnecessary re-render, as it is stated in the doc:
With useSelector(), returning a new object every time will always force a re-render by default.
But then I noticed that my component was only re-rendering when data changed. I assumed that maybe the default equality function works with objects, but to be sure I tried to use a custom function:
const data = useSelector(state => state.data, (a, b) => a === b));
If data didn't change, it will always return true.
In Javascript, if I try to compare two objects with === it will return false because the references are different. But here with useSelector(), since it returns true it means that the references are the same.
Now, I'm not sure if I misunderstood the doc, but I wonder when I should use a custom equality function such as the isEqual of Lodash?
I'm using Redux Toolkit, if it makes any difference. Here is a CodeSandox if you want to test.
You should rarely need to write a custom equality function for useSelector.
Most of your useSelector calls should either be a simple const item = useSelector(state => state.slice.item), or be pass in an existing memoized selector function like const someData = useSelector(selectSomeData).
If you need to pass in a custom equality function, the most common one would be the shallowEqual function exported from React-Redux itself.
If you think you need to use a deep equality check like _.isEqual, you should probably pause and reconsider why you think you need a deep equality check, because that should normally be unnecessary in a Redux app.
Additionally, I think there's a bit of a misunderstanding here. Yes, state.data is an object, but it is a reference to an object in memory. When your reducer updates, RTK+Immer will automatically create a new object reference, and thus oldObject === newObject will be false because two different objects are never === to each other. That's why useSelector already uses a === comparison by default - immutable updates means that comparison is really fast and simple to do.
I'm learning about immutability and pure functions. As a newbie, I'm struggling and I don't understand if the following code mutates or not the state or data.
This is the code
let state = [];
const updateState = (state, data) => {
return [...state, data];
}
state = updateState(state, 1);
console.log(state);
I want to use a pure function that receives a state and data and updates the original state.
This line state = updateState(state, 1); feels like a mutation for me, but I'm not sure. Am I mutating the state?
Thanks for your help. I'm trying to learn.
Yes, your code mutates state exactly where you think it does. updateState is a pure function because it performs no side effects, doesn't mutate anything, and will always have the same return values given the same inputs, but reassigning state is a mutation. This is also what libraries like Redux do under the hood, for example here. Note that the important thing is that the current state isn't being mutated in place, it's being completely reassigned to the value of the new state as computed by a pure function. JS isn't a great language for purity and immutability, but if your whole app was just composed of functions and immutable data (with some IO at the edges), it would be possible to never need to reassign or mutate anything (see also this question and the top answer).
Scenario:
I have two components: X and Y. Component Y is the child of component X.
inside X:
<Y data={FormObject} />
Inside the constructor of component Y, I add the prop data (FormObject) to the state of Y which works well. Now after that, if I change the state it affects the props. How? When I close component Y and reopen it I see that the last state is showing up. FormObject isn't like how it was at the beginning. This is because objects are reference types right?
Is there a solution for this? The only solution i found is to do somthing like this:
Inside constructor of Y:
const { data } = JSON.parse(JSON.stringify(this.props);
Does it seem like JSON.parse() or JSON.stringify() changed the reference by making a copy with a different memory number?
Is there a better way of doing this instead of the mentioned solution?
If your FormObject has no child object or array, you can fix that by using object destructor like below:
const data = { ...this.props.data }; // it's a kind of shallow copy
But if it has some object or array in it, it won't prevent mutations.
So you need to deep copy using JSON.pares(JSON.stringify(data)) or Recursive method.
ok if you have to have new copy of the props in your state you can use some library for cloning the deeply nested object, you can use deepClone method from lodash.
I would also recommend taking a look at this from react documentation
hope it helps.
I basically understand the difference between object and function setState in React in terms of atomicity and resiliency to batch updates.
// Subject to batching issues.
this.setState({
x: this.state.x + 5
});
Or should I use:
// Atomic since previous state is known
this.setState((state, props) => {
x: state.x + 5
});
However, I wonder what could be the advantages of using functional setState if previous state is irrelevant.
this.setState(() => {
food: "Bars"
});
I don't think there are any advantages, as they are basically the exact same function call, just using an object vs a function. So if your update doesn't depend on the current state or current props, then use an object. If it does require previous knowledge, use the function type.
Both versions also allow you to use a callback to access the state after the state has changed, so there is no difference in abilities either.
The only reason I might use the function version everywhere is just for consistency, or in situations where I am currently not using the previous state, but might in the future.
setState with object as parameter does shallow comparison with previous state.
If your value is dependent on previous state it is good practice to use setState with function(as setState is async operation) otherwise just pass Object as parameter.
There should not be performance issue in either case.
We have a project using React + Redux + ImmutableJS. One of our engineers recently added a helper method to support destructuring an ImmutableJS Map when passing it to a component as props:
export function toObjectShallow(mapping: Map) {
const result = {};
mapping.map((value, key) => {
result[key] = value;
});
return result;
}
Thus, we can still do the following and avoid being verbose with repeated calls to Map.get:
<XyzComponent {...toObjectShallow(this.props.xyz)}/>
Yes, that's essentially making two shallow copies (our method + destructuring) of the original object. That should be of minimal expense. I'm wondering though, since I don't see this kind of recommendation really anywhere else in the React/Redux/Immutable communities, is there something else I'm missing that would make this unideal?
The passed properties are still the original, immutable props. It's just the containing object is mutated which doesn't matter, because it's not getting passed to the component anyways. So, what gives? This seems like such a simple solution while avoiding toJS(). Why isn't it really mentioned anywhere?
I followed the advice in the redux docs to use a HOC for all connected components to allow me to interact with plain javascript objects outside of redux. So my selectors and reducers still use ImmutableJS objects, but the rest of the code uses plain javascript objects:
https://redux.js.org/docs/recipes/UsingImmutableJS.html#use-a-higher-order-component-to-convert-your-smart-components-immutablejs-props-to-your-dumb-components-javascript-props
edit- not sure if this is the toJS you are mentioning above, I had assumed you meant ImmutableJS.toJS.
as far as preferences, using an HOC you only need to do it once per component, as opposed to each time you use a component in your method.
I think the "correct" answer I was looking for was to continue using Immutable JS's toObject() which does a shallow conversion of an Immutable Map object to a regular object, thus allowing us to continue using syntactic sugar while keeping the properties immutable and not having to use our own shallow copy.