What exactly is prevState in a functional component's setState? - javascript

Returning the (changed) previous state within a setState that one gets from the useState hook doesn't seem to alter state. Run the following straight forward snippet
function App(){
const [state, setState] = React.useState([{x: 0}])
function changeCount(){
setState(prevState => {
console.log('before', prevState[0])
const newState = [...prevState]
newState[0].x += 1 //the shallow copy newState could potentially change state
console.log('after', prevState[0])//here x gets bigger as expected
return prevState //instead of newState we return the changed prevState
})
}
//checking state shows that x remained 0
return <div className='square' onClick={changeCount}>{state[0].x}</div>
}
ReactDOM.render(<App/>, document.getElementById('root'))
.square{
width: 100px;
height: 100px;
background: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id='root'></div>
By clicking the square we trigger setState. Within setState we make a shallow copy newState of the previous state. By changing the copy we change the prevState (maybe unintentionally) as the console confirms. Returning the changed previous state form setState however doesn't change the state as the count remains 0. If we were to return the newState, the behavior would be as expected.
Repeating this, shows that prevState gets bigger, it just doesn't seem to represent the previous state anymore.
Why is that? I made this minimal example on codepen...

Consider that object assignment is just a reference assignment, never a copy
obj1 = {x:42, y:99};
obj2 = obj1; // obj1 and obj2 both reference the same object
obj1.x += 1;
obj2.y += 1;
console.log(obj1.x, obj1.y);
console.log(obj2.x, obj2.y); // prints the same thing since obj1 and obj2 are the same object
In the above example, obj1 is initialized to point to a new object with properties x and y. When obj2=obj1 is made, this is not a copy of obj1 into obj2, but rather obj1 and obj2 now reference the same object.
Hence, when the console.log statements print, they print the same thing because they are both printing property values from the same object.
Similarly, the additional reference to the original object is being made when the shallow copy from prevState to newState occurrs.
obj = {x:42, y:99};
prevState[0] = obj; // prevState[0] is a reference to obj. prevState[0] and obj point to the same exact thing
newState = [...prevState]; // shallow copy, but newState[0] is an object reference. newState[0] and prevState[0] both point to "obj"
newState[0].x += 1; // is actually updating the original object assigned to prevState[0]

Related

issues with redux payload in reducers

I have the following code for one of the CASES in reducers
case CHART_CHANGE: {
const {method,copiedArray} = payload;
let ref = copiedArray
let swapped = bubblesort(copiedArray) //<------here
console.log(ref)
console.log(swapped)
}
}
The question is this, as you can see here I assigned the variable ref before involving the bubblesort function. But when I run the program, ref variable has the same value with the swapped variable. Why is that?
In javascript objects (arrays are objects) are assigned by reference, so copiedArray and ref are referring (pointing) to the same object. If you change the contents of the object (and your bubblesort function seems to sort in place - that is mutate/change the array internally), the changes are visible in all of the variables: copiedArray, ref and swapped.
const copiedArray = [3,2,1];
const ref = copiedArray;
const swapped = copiedArray.sort();
console.log(copiedArray);
console.log(ref);
console.log(swapped);

Redux: cloning state does not work

I have a very simple question, but…
The code (in a redux/react-native app) of a reducer:
...
case SAMPLES_DELETE_REQUEST_SUCCESS: {
var newState = Object.assign({}, state);
const indexToDelete = newState.samples.findIndex( sample => {
return sample.id == action.sample.id
})
newState.samples.splice(indexToDelete, 1)
debugger;
return newState
}
...
Ok, I copy the state and store it into newState. But when I do newState.samples.splice(indexToDelete, 1), newState is correctly modified, but also state! Why?? I must be tired…
splice function modifies original array. Object.assign does not do deep cloning. Therefore you are still modifying the original state!
You will have to manually copy the nested object(or array) you want to clone:
// Deep Clone
obj1 = { a: 0 , b: { c: 0}};
let obj2 = JSON.parse(JSON.stringify(obj1));
As someone mention before you could use JSON.parse(JSON.stringify(obj)) to create a new copy of the entire object (nested object as well). If you don't want to do that, you could check libraries like Inmutable JS
Also if you want to use spread notation, a better way to do that will be:
return {
...state,
samples: state.samples.filter(sample => sample.id !== action.sample.id)
}

State mutated by getter

I'm trying to write a getter that returns a simple number from state, decremented by 1
const getters = {
getCurrentView: state => {
return types.PAGES_OBJECT[state.currentViewNum]
},
getCurrentViewNum: state => {
return state.currentViewNum--
}
};
However this actually mutates the state.
I have tried assigning it to a var, but it appears that var becomes a direct reference to state.
How do I do this, without mutating state?
Isn't x-- equivalent to x = x - 1?
The -- operator is mutating your object.
return state.currentViewNum - 1 instead

Calculate state recursively using map/reduce?

I have an array of objects, let's say
let objs: foo[];
an immutable state object
let state: State;
and a function
transform: (state: State, obj: foo) => State;
So transform calculates a new State from the previous one using the information from the current obj; if you're thinking 'sounds like Redux to me' you could be right.
Is there a way to call transform recursively for each object from the array such, that each calculated state is the input parameter for the next call to transform using map and reduce? I'm only interested in the final state.
I tried something like
let finalState = objs.reduce((prev: State, curr: foo) => transform(prev, curr), state)
but as reduce requires prevand curr to be of type foo (the array type) this is not going to work obviously.
You can use the current index provided by Array.prototype.reduce:
return objs.reduce((prev, next, index) => transform(prev, objs[index]), state);
But this way you're kind off abusing the reduce function. Imo this should and could be done with a regular forEach statement:
function getFinalState(state: State, objs: Obj[]) : State {
objs.forEach(x => state = transform(state, x));
return state;
}
See this TypeScript playground for working samples

React: Why is it necessary to have immutable props if values are passed as by value anyway?

I not too deep into JavaScript. Therefore have done this tinkering:
var demo = 'test';
var obj = {
x: 'abc'
}
function foo(str) {
str += '_123';
return str;
}
var ret = foo(demo);
var retObj = foo(obj.x);
console.log('Result: %s', ret); // => 'test_123'
console.log('Used parameter: %s', demo); // 'test'
console.log('Result: %s', retObj); // 'abc_123'
// The value of the property is still unchanged. So
// it has been passed in as by value. As as copy.
console.log('Used parameter: %s', obj.x); // 'abc'
I pass a string to the function. One time as primitive variable and the next time as object property.
Afterward the original data are unchanged. => The outer system is unchanged.
Now I am wondering:
Why do they have these immutability feature in React?
I mean: They say a function shall do something and provide as result. But not changing the system as whole.
But if data are passed as by value anyway? Why do the make such a big thing out of having immutability?
In both examples you pass a string to the function. obj is an object, but obj.x is a string.
JavaScript treats strings and numbers (primitives) as immutable. So if you do:
var a = "bob";
var b = a;
b = "jack";
The original string a will be unchanged.
But objects are different. If you do:
var propsObj = { name: "bob" };
var newPropsObj = propsObj;
newPropsObj.name = "jack";
Then propsObj will also change, so propsObj is now { name: "jack" }.
React uses stuff you pass as props and state to make its virtual DOM. Because react uses these to see if anything changed, and render only stuff that did change, react relies on your code not to change props or state after you pass it to react.
In the second example, this will cause react to think that new and old props are still the same, so react will (incorrectly) conclude that it does not need to re-render.

Categories

Resources