Why do new objects are created on each React render? - javascript

I was working with some code in React that had an infinite loop caused by useEffect and a function declared outside of the useEffect callback, after some research I found out that this is because on each re-render React creates a new inner function and the solution was to use the useCallback hook.
Still, I want to know why this is the behavior of objects (array, functions, objects) declared inside functions as opposed to values(strings, numbers, booleans, etc.) that seem to maintain their place in memory and aren't being created from scratch on every function call. I tried googling about it but didn't find anything.
Here's an example of what I'm referring to:
https://codesandbox.io/s/sweet-mccarthy-lf32r?file=/src/App.js
When you click the increase button, the effect that depends on the array, triggers, but the one depending on the number doesn't.

Ok, I think I got the answer, but I would like to be corrected if I'm wrong:
So for this to make sense we need to understand 3 things:
Garbage collection
Primitive vs References
How equality comparisons work
First, to address my previous statement:
I want to know why this is the behavior of objects (array, functions,
objects) declared inside functions as opposed to values(strings,
numbers, booleans, etc.) that seem to maintain their place in memory
and aren't being created from scratch on every function call.
Both primitives and references are created from scratch on every function call due to garbage collection, for more info check https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management
Primitives are saved into the stack and objects are stored in the heap and the object gets "pointer" to the location in the heap. More on that in this video
The trick with the dependencies array in useEffect has to do with how equality works in JavaScript, a great article about this tell us that:
If both operands are numbers and have the same value, they are
strictly equal
If both operands are strings and have the same value,
they are strictly equal
If both operands have reference to the same object or function, they
are strictly equal
So if we have:
function App() {
const num = 1;
const arr = [1, 2, 3];
const [state, setState] = React.useState(1);
React.useEffect(() => {
console.log("updated by change of num");
}, [num]);
React.useEffect(() => {
console.log("updated by change of arr");
}, [arr]);
return (
<button onClick={() => setState((prev) => prev + 1)}>
increase {state}
</button>
);
}
The dependency array doesn't care if num has the same location in memory (which it doesn't due to being new in each function call) as the equality only checks for type and value, but it does care the place in memory for objects as for them to be equal they need to have the same reference.
Hope this is clear!

Related

Objects are equal even though properties are not

I've recently ran into quite an interesting problem with my Typescript + React code. The problem I am running into is as follows:
I trigger a method on my Trip object called updateStop when a new stop is received from a websocket connection. This method has the following implementation:
public updateStop(stop: PassTimeUpdate): Trip {
const stops = this.getStops();
const stopToUpdate = stops.find(s => s.userStopCode === stop.UserStopCode);
if(!stopToUpdate)
return this;
stopToUpdate.updateStop(stop);
return this;
}
The getStops method has the following implementation:
public getStops(): IteneraryStop[] {
return this.itenaries.flatMap(i => i.stops);
}
And the updateStop method has the following implementation:
public updateStop(updatedStopData: PassTimeUpdate) {
console.log(`Updating stop: ${this.name} from ${this.tripStopStatus} to ${updatedStopData.TripStopStatus}`);
this.tripStopStatus = updatedStopData.TripStopStatus;
}
The hierarchy is as follows:
Trip has Iteneraries has Stops.
Now my problem comes when I want to compare the previous version of the trip to the new version of the trip.
if(oldTrip.getStops() === updatedTrip.getStops())
This results in false, obviously, as the stops have been updated and thus are not the same. However, the following:
if(oldTrip === updatedTrip)
Results in true. I've looked on the internet for some way to "clone" a object, as I think the problem is with that the signature of the objects matches, even though their content isn't the same?
Due to the oldTrip and updatedTrip matching, react doesn't trigger the useEffect hook when calling setTrip(updatedTrip) as it thinks oldTrip and updatedTrip are the same.
What would be a good way to force the trips to be "different"? I have tried having some kind of internalID string which I update every time the updateStop method is executed, however this still results in the same behaviour.
I'm assuming that oldTrip and updatedTrip are instances of Trip. It sounds like you are probably trying to manipulate an instance which is stored at different points into oldTrip and updatedTrip and then are thinking that the comparison of those variables will not be the same as it was before.
What you are actually doing it comparing the same instance of Trip with itself. When you put the instance into a var, it didn't copy it. It just made another pointer to the same one.
The comparison is true, since even oldTrip is just a pointer to that same instance. Whenever you manipulated the instance using then methods, oldTrip would of changed as well, as it's just a pointer to the instance. And when you compare them the JS evaluator asks "are these the same instance" which is true.
In addition, when you do a comparison operator on any object (that includes arrays, instances of classes, etc) like this, what it does not do is say "are the contents different". In the first example, it will return false every time since in JS
[1,2,3] == [1,2,3] will return false since again, it is comparing pointers and here there are two new arrays initialized on both sides. It does not implicitly compare the contents.
One reason people fall into this trap is that comparing primitives like strings, numbers will work fine. But those are not objects and so you aren't comparing a reference, but the real underlying value. more info here.

When should I use a custom equality function in useSelector()?

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.

Lodash uniqWith comparator function modify object properties

I want to modify each object in an array, as well as remove duplicates. I am already using lodash's uniqWith to compare items so I figured I would do some other logic within the comparator function to modify each item so I can avoid setting up another loop. Are there any problems with having extra logic within the comparator like below?
import uniqWith from 'lodash/uniqWith';
const transformedArray = uniqWith(
arrayOfObjects,
(currObject, otherObject): boolean => {
// modifying current object's properties, is this legit??
if (<someCondition>) {
currObject.someProperty = true;
}
// actual comparison logic
if (currObject.uuid === otherObject.uuid) {
return true;
}
return false;
},
);
The uniqWith documentation only says the following about the callback invocation:
The comparator is invoked with two arguments: (arrVal, othVal).
I personally wouldn't use the uniqWith compare function, for iteration work. The documentation doesn't disclose how often or in what order the compare function will be called. Say you have the array [1,2,3]. I would assume each element is compared against all elements in the output, to check for uniqueness. The implementation might look something like:
function uniqWith(array, compareFn) {
const unique = [];
for (const current of array) {
const isDuplicate = unique.some(other => compareFn(current, other));
if (!isDuplicate) unique.push(current);
}
return unique;
}
If this is indeed the case that means the callback is never called for 1, once for 2, and twice for 3.
Generally speaking don't use callback functions to do iteration work, unless the function that accepts the callback discloses when/how often/what order etc. this function is called.
Take for example the Array forEach method. The documentation discloses all these things.
forEach() calls a provided callback function once for each element in an array in ascending order. It is not invoked for index properties that have been deleted or are uninitialized. (For sparse arrays, see example below.)
Short answer: no, if by "wrong" you mean a breaking issue.
Medium Answer: It shouldn't interfere with the method. Just be aware that doing mutations in unique locations, where you otherwise may not expect them, is trading clarity for brevity- which can lead to confusion and debugging nightmares later down the road if you lose track of them.
After more thorough testing I realized that there is some optimization done in lodash/uniqWith where not every item is necessarily passed to the comparator function as the currObject. So as far as my example code in the original question, it is not guaranteed that every item in array will receive the .someProperty = true. Should've tested this more before posting, but still appreciate the feedback.

Why is a Redux reducer called a reducer?

Whilst learning Redux I've came across Reducers. The documentation states:
The reducer is a pure function that takes the previous state and an action, and returns the next state. (previousState, action) => newState. It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue) .
MDN describes the reduce method as:
The reduce() method applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.
I'm still confused on why the Redux definition of a reducer as it's making no sense. Secondly the MDN description doesn't seem correct either. The reduce method isn't always used to reduce to a single value. It can be used in place of map and filter and is actually faster when used in place of chaining.
Is the MDN description incorrect?
Jumping back to the Redux definition of a reducer, it states:
It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue)
I'm under the impression that a reducer in Redux is responsible for modifying state. An example reducer:
const count = function(state, action) {
if(action.type == 'INCREMENT') {
return state + 1;
} else if(action.type == 'DECREMENT') {
return state - 1;
} else {
return state;
}
}
... I don't see how this is a function that would be passed to reduce. How is that data being reduced to a single value? If this is a function you would pass to reduce then state would be the callback and action would be the initial value.
Thanks for any clear explanations. It's difficult to conceptualize.
The term "reduce" is actually a functional term used in functional programming. In a language like Haskell, F# or even JavaScript, we define a transformation that takes a collection (of any size) as input and returns a single value as output.
So (not to be pedantic, but I find this helps me) think of it visually. We have a collection:
[][][][][][][][][][]
...which we want to collapse into a single value:
N
Programming functionally, we would do this with a single function that we could call recursively on each element of the collection. But if you do that, you need to keep track of the intermediate value somewhere, right? Non-pure implementations might keep some kind of "accumulator" or variable outside of the function to keep track of the state, like so:
var accumulator = 0;
var myArray = [1,2,3,4,5];
myArray.reduce(function (each) {
accumulator += 0;
});
return accumulator;
With pure functions, though, we can't do this - because by definition, pure functions can't have effects outside of their function scope. Instead of relying on an external variable that encapsulates our "state" between calls, we simply pass the state along in the method:
var myArray = [1,2,3,4,5];
return myArray.reduce(function (accumulator, each) {
return accumulator + each;
}, 0);
In this case we call the function a "reducer" because of its method signature. We have each (or current - any name is fine), representing an object in the collection; and state (or previous), which is passed to each iteration of the function, representing the results of the transformation we've already done to the previous elements in the collection.
Note that the MDN documentation you referenced is correct; the reduce() function always does return a single value. In fact, the reduce method in any language is a higher-order function that takes a "reducer" (a function with the method signature defined above) and returns a single value. Now, yes, you can do other stuff with it, if your function that you call has side effects, but you shouldn't. (Essentially, don't use .reduce() as a foreach.) Even if the method you call with reduce has side effects, the return value of reduce itself will be a single value, not a collection.
The cool thing is, this pattern doesn't just have to apply to arrays or concrete collections, as you've seen in React; this pattern can be applied to streams as well, since they're pure functions.
Hope this helps. For what it's worth, the definition on the Redux site could be improved (as the concept of a reducer isn't just because of Javascript's Array prototype method). You should submit a PR!
Edit: There's a Wikipedia article on the subject. Note that reduce has different names, and in functional languages, it's commonly known as Fold. https://en.wikipedia.org/wiki/Fold_(higher-order_function)#Folds_as_structural_transformations
Edit (2020-10-03): People still seem to be finding this useful - that's good. With time, I've realized that "fold" is a much better term for this; the functional languages got it right. "Reducer" isn't really a bad term, but it's not necessarily a good one, either.
The reason why a redux reducer is called a reducer is because you could "reduce" a collection of actions and an initial state (of the store) on which to perform these actions to get the resulting final state.
How? To answer that, let me define a reducer again:
The reduce() method applies a function (reducer) against an accumulator and each
value of the array (from left-to-right) to reduce it to a single
value.
And what does a redux reducer do?
The reducer is a pure function that takes the current state and an
action, and returns the next state. Note that the state is accumulated as each action on the collection is applied to change this state.
So given a collection of actions, the reducer is applied on each value of the collection (from left-to-right). The first time, it returns the initial value. Now the reducer is applied again on this initial state and the first action to return the next state. And the next collection item (action) is applied each time on the current state to get the next state until it reaches the end of the array. And then, you get the final state. How cool is that!
Sorry, but I would disagree with previous answers. I would not support the naming reducer. I'm passionate about FP and immutability. Don't blame me, read the second part, but I want to state first, why I disagree.
It's correct that the reducers are the sequence of transformations, but the sequence itself - could be part of another sequence. Imagine it, like links - a part of chain. But the chain itself could be part of longer chain. Each link is the "transition" of the global state. Than, what the theory behind it?
Isn't it actually the "Finite state machine"? - close, but not. It's actually the Transition system.
A labelled transition system is a tuple (S, Λ, →) where S is a set of states, Λ is a set of labels and → is a set of labelled transitions
So, S - are set of our states
Λ - is our so-called "actions" (but labels in theory)
... and
→ - reducers "labelled transitions"! I would name it so, if I am creator of this library.
Understanding this theory helped me to implement my library, where I can have low-level transition system as a part of high-level transition system (like chain - still could be part of longer chain) - and still having single global Redux state.
Calling Redux reducers reducers is semantically incorrect and doesn't make much sense.
That's why the author is confused.
A reducer is a function that reduces a set of values to a single value.
We can also say that it folds the values - thus the classic fold() fn in functional programming.
Since Redux reducer does not fold a set of value, but applies an action to a state and always returns the same shape (State -> Action -> State) - it should be called applicator or applier.
But, since we have to always return the same shape of the state, and not just smth completely unrelated - we'd make much more sense calling Redux state applicators - changers, transformers or mutators.
And, it has indeed become commonplace to use terms like 'mutate the state' and 'state mutator'.
But Redux sounds just so much cooler, than Applux or Mutux :)
I'm under the impression that a reducer in Redux is responsible for modifying state. An example reducer:
const count = function(state, action) {
if (action.type == 'INCREMENT') {
return state + 1;
} else if (action.type == 'DECREMENT') {
return state - 1;
} else {
return state;
}
}
... I don't see how this is a function that would be passed to reduce. How is that data being reduced to a single value? If this is a function you would pass to reduce then state would be the callback and action would be the initial value.
// count function from your question
const count = function (state, action) {
if (action.type == 'INCREMENT') {
return state + 1;
} else if (action.type == 'DECREMENT') {
return state - 1;
} else {
return state;
}
}
// an array of actions
const actions =
[ { type: 'INCREMENT' }
, { type: 'INCREMENT' }
, { type: 'INCREMENT' }
, { type: 'INCREMENT' }
, { type: 'DECREMENT' }
]
// initial state
const init = 0
console.log(actions.reduce(count, init))
// 3 (final state)
// (INCREMENT 4 times, DECREMENT 1 time)
These answers are good but I think it's much simpler than you think. I'll admit that I had a similar confusion at first. The reduce method on Javascript arrays takes an accumulator and a callback function.
const arr = [1, 2, 3]
const sum = arr.reduce((accumulator, element) => {
accumulator += element;
return accumulator;
}); // sum equals 6 now
The reason it's called a reducer in redux is because it roughly has a similar structure.
const sum = arr.reduce((accumulator, element) => { // accumulator is the initial state
accumulator += element; // we do something to modify the initial state
return accumulator; // we return that and it becomes the new state
});
So every time we run a reducer we take something in, modify it, and return a copy of that same thing. On each iteration we're pointing to the same thing. Ok, yes, we have to make a copy in redux so as to not directly modify state, but symbolically every time we run it, it's kind of like the way reduce starts with an initial state in the example above of 1. Then we add 2 to our inital state and return 3. Now we run our 'reducer' again with an intial state of 3 and add 3 and we end up with 6.
It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue)
Array.reduce
This is very similiar to what you would pass to Array.reduce as the callback (reducer). The important part being:
callback
Function to execute on each value in the array, taking four arguments:
previousValue
The value previously returned in the last invocation of the callback, or initialValue, if supplied. (See below.)
currentValue
The current element being processed in the array.
Where state is the "previousValue" and action is the "currentValue".
Both Array.reduce and Redux take a "reducer" function, which reduces previous and current into a single return value.
const reducer = (previous, current) => {
/* calculate value */
return value;
};
previous
current
return
Array.reduce
accumulator
current element
new accumulator
Redux
state
action
new state
Redux docs:
If we were to create an array of Redux actions, call reduce(), and
pass in a reducer function, we'd get a final result the same way:
const actions = [
{ type: 'counter/incremented' },
{ type: 'counter/incremented' },
{ type: 'counter/incremented' }
]
const initialState = { value: 0 }
const finalResult = actions.reduce(counterReducer, initialState)
console.log(finalResult)
// {value: 3}
We can say that Redux reducers reduce a set of actions (over time)
into a single state. The difference is that with Array.reduce() it
happens all at once, and with Redux, it happens over the lifetime of
your running app.
It's a confusing name because in practice it has nothing to do with Array.prototype.reduce.
In the Redux source code here, this is how your reducer is called:
currentState = currentReducer(currentState, action)
It's enticing to use the term because your state at any point in time could be found by calling arrayOfPreviousActions.reduce(reducer).
['INCREMENT', 'INCREMENT'].reduce((currentState, action) => currentState++, 0)
But it's not actually called like this.
The real question is what would be a better name for this?
If we don't consider the output/purpose, it's handling an action/event. An actionHandler, actionProcessor.
Aside: event is probably a better term than action too because Redux is often also used to respond to API responses which results in action types set to GET_USERS_SUCCESS...which is clearly an event. This was simply adopted from Flux though.
Redux draws inspiration from the Event Sourcing pattern and State Machines/Transition Systems. Googling around revealed the term state transition often used.
state transitions can be represented as functions that take a state and an event, and produce a new state
But what do we call these "functions"?
IMHO, reducer is a poor name because it doesn't encode any meaning about its purpose, just what it looks like, but I can't think of a better one.
In the end, all names for things were invented at some point, and given its popularity, reducer is now associated with state reducer or state transition function.
Just look at the origin of the term argument for example:
The use of the term "argument" developed from astronomy, which historically used tables to determine the spatial positions of planets from their positions in the sky. These tables were organized according to measured angles called arguments, literally "that which elucidates something else."
It's not a very specific name, but the reducer in Redux does the same thing that other reducer functions do. They take in multiple things and give us back one thing. The Redux reducer takes in two things (the previous state and an action to be performed on that previous state) and reduce those two things down to one: the next state.

Are there any benefits in storing arrays inside of a function vs inside object

Are there any benefits at all to storing arrays inside of a function vs storing arrays inside objects? The values stored in these arrays will not change, but there could be thousands of lookups. I am wondering if I should switch to objects now.
function arrayFunc (arr) {
if(arr=='one'){
return [99,2,3,4,5,6,7,98,9,341,82,36,44,5,55,6,3,66,77,45,241,144,1,334];
}
else if(arr=='two'){
return [83,2,3,4,5,6,7,8,9,11,22,33,44,5,55,6,3,726,77,125,241,144,1,65];
}
else if(arr=='three'){
return [64,2,56,95,5,544,7,8,9,11,22,33,44,5,55,6,3,66,77,45,241,144,1,2];
}
};
var x=arrayFunc('two')[index];
By object I mean something like this
var arrayObj = {
one:[99,2,3,4,5,6,7,98,9,341,82,36,44],
two: [83,2,3,4,5,6,7,8,9,11,22,33],
three: [64,2,56,95,5,544,7,8,9,11,22]
};
Yes, there are two major improvements: performance and code legibility.
Performance: You will not be recreating the array every time, and with hashed keys you will not be checking equality on every key.
Code legibility: This is the standard way to hold data because it is easier to read. Follow the convention.
In your code, the array will be re-created every time you call the function. This is a significant overhead if you are basically indexing into the array once.
It is not entirely clear to me what you mean by store in object vs. function, but if by that you mean that the arrays are owned by an object that is repeatedly used and not recreated every time, then yes, the object way will be preferred.

Categories

Resources