Copying two-dimensional array from state in React.js - reactjs

I'm attempting to build a small react app to model Conway's Game of Life. I've set up a two-dimensional array to track the state of each cell in a 10-by-10 grid.
I'm attempting to store this array in the State. At each "tick" of the game, I want to make a copy of the array, evaluate each cell, potentially giving it a new value, and then assign the copy back to state. I'm basing this off of the official React Tutorial where they use this exact approach:
handleClick(i) {
//Make a copy from state
const squares = this.state.squares.slice();
//Make some changes to it
squares[i] = 'X';
//Set state to the new value
this.setState({squares: squares});
}
My initial approach was to use slice() as in the example above. Through debugging, I discovered that this didn't work; somehow state is being changed even though I have used various methods to copy it that shouldn't make changes to it. (I understand that if I say var x = this.state.blah and x = 5 that I have changed state because blah is a reference to it)
Here is my code:
doTick = () => {
console.log("doin a tick");
console.log(this.state.squares);
//None of these approaches works
//Three different copy strategies all fail
//const newSquares = Object.assign({}, this.state.squares);
//const newSquares = [...this.state.squares];
//const newSquares = this.state.squares.slice();
const newSquares = this.state.squares.slice();
const origSquares = [...this.state.squares];
//Iterating over the array
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
newSquares[i][j] = evaluateCell(origSquares[i][j], this.countLiveNeighbors(i, j, origSquares));
//evaluateCell(origSquares[i][j], this.countLiveNeighborsAndLog(i, j, origSquares));
}
}
//this.setState({
//squares: newSquares
//});
}
Even though the setState() call is commented out, just having the assignment of newSquares[i][j] = //... is enough to somehow modify state.
Here's the code where I set up the initial array in the constructor for the Board component:
constructor(props) {
super(props);
var array = new Array(10);
for (var i = 0; i < 10; i++) {
array[i] = new Array(10).fill(false);
}
this.state = {
squares: array
};
console.log(this.state.squares);
}
I took a look here but I'm not having any trouble updating the squares based on clicks (that part of my code works fine). Various SO posts and in-person troubleshooters suggested the three different copy strategies that all produce the same problem. I also took a look here.
I'm very new to React and not very skilled in JS generally, and obviously I don't have a good handle on State. Here are my questions:
How can I make a copy of state/part of state/data in state in such a way that the copy is not a reference to state? I want to be able to change this new data without changing state (until I am ready).
Why are the methods used above NOT working properly? The documentation for slice() assures me that I'm getting a copy and not a reference.
Thanks in advance! I'm very confused.

The spread operator only does a shallow copy of the values. This means if you have any nested values in them, they will be referenced rather than copied. For instance:
const a = { field: { innerField: 'test' } };
const b = { ...a } // b === { field: { innerField: 'test' } } SAME field as a
To copy a nested array you should use deep copy methods, such as Lodash's cloneDeep or Ramda's clone
for example, with Lodash's cloneDeep:
const newSquares = _.cloneDeep(this.state.squares);

Related

unable to set record in react usestate array from sqlite

I am using sqlite for local storage in react native but when I tried to fetch record and set it into the useState , it doesn't update the state
here is the code
const SelectQuery=async()=>{
const token = await AsyncStorage.getItem('docToken')
let selectQuery = await ExecuteQuery("SELECT * FROM DoctorConversations where d_phone=?",[token]);
var rows = selectQuery.rows;
var temp = [];
for (let i = 0; i < rows.length; i++) {
temp.push(rows.item(i))
}
return temp;
}
This is how I am calling
SelectQuery().then(res=>res.map(item=>{
setFlatListItem([...flatListItem,item])
}))
I guess that you have:
const [flatListItem, setFlatListItem] = useState([]);
setFlatListItem changes internal state and schedules a rerender. flatListItem gets a new value only on the next render.
You need to use functional updates to modify the current internal state:
setFlatListItem(state => [...state, item])
If you want to replace the whole list, you do not need to map individual items:
SelectQuery().then(res=> setFlatListItem(res))
Note that flatListItem is a bad name in your case since it holds an array. You should probably rename it to just [flatList, setFlatList].

updating one value in State array react native

Trying to update one element of an array in this.state I'm getting an (expected ,) error but cant see where I've gone wrong. Do I need to create a temporary array update that, then assign the whole array back to the state
This is essentially what I have
this.state = { array: ['a', 'b', 'c'] };
onBack() {
this.setState({
array[2]: 'something'
});
}
You can't update the state like this.
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
Read React docs.
You can do something like this :
let newArray = [...this.state.array];
newArray[2] = 'somethingElse';
this.setState({array: newArray});
The above example is using Spread Syntax.
There are multiple ways to modify state, but all the ways should ensure that data is treated as immutable. You can read more about handling state in React here
Manoj's answer will work, there is another way using the updater function (funtinal setState) for setState (which you can read about here)
onBack() {
this.setState(prevState => {
let newArray = prevState.array
newArray[2] = "something"
return { array: newArray }
});
}
Use Object.assign({});
let array= Object.assign({}, this.state.array); //creating copy of object
array[2]="something"
this.setState({array});
If you are bother to set the new updated array on your currentArray using spread function, you may use Array.from(duplicateArray)
let duplicateArray = [...currentArray] //duplicate the array into new array
duplicateArray[index].value = 'x'; // update the field you want to update
setCurrentArray(Array.from(duplicateArray )) //set the new updated array into currentArray
I bother with this issue and I just search about and I saw this idea, and it works!
https://github.com/facebook/react-native/issues/21734#issuecomment-433885436
using hooks
const [totalSet, setTotalSet] = useState(null);
console.log(totalSet); // {sets: Array(3), subscribed: false}
setTotalSet(datas => ({
...datas,
sets: updatedSet,
}));
Create a temporary array update that, then assign the whole array back to the state
var arr = [];
arr = this.state.array;
arr[2] = "something";
this.setState({
array : arr
});

React Redux: View not getting updated even though mapStateToProps is

My Application and Store
Using Redux with ReactJS, I am keeping an array of objects (called results) in the store and dispatching actions that sort and manipulate it. In mapStateToProps, I return this results array, which renders the results in a list on the view.
// in Component file
export default class ResultList extends Component {
constructor(props) {
super(props);
this.renderResults = this.renderResults.bind(this);
}
renderResults(results) {
return (
results.map((result) => {
return <AnotherComponent />
})
);
}
render() {
const { results } = this.props;
return (
<div>
<ul>
{this.renderResults(results)}
</ul>
</div>
)
// in Container Component file
const mapStateToProps = (state, ownProps) => {
console.log('map state is triggered');
return {
results: state.results
};
}
The Problem
I have found that although my mapStateToProps successfully triggers to update the props to my class component, for some reason the view only updates on the FIRST time that the results array gets manipulated, but does not update on future updates to the store. (Please see update below. This is not entirely accurate.)
I have made sure that this problem is not due to mutating state in my reducers as is often the case; I have confirmed that the mapStateToProps runs every time that the store gets updated (as indicated by the console.log). The problem seems to be between the results props getting returned from mapStateToProps and the view actually rendering the results array, but I do not have visibility to see what Redux is doing under the hood.
The closest problem that someone else has had to what I am experiencing seems to be this, but I do not know how or if this fix applies to my use of a stored array: https://forums.meteor.com/t/the-state-is-correctly-mutated-and-returned-but-view-does-not-rerender/28840/5
Any help would be greatly appreciated. Thank you.
Update
I apologize, but must correct my statement above saying that there are no further updates to the view after the first time the results in the store gets updated. With further testing I have found that is not completely true. The view gets updated only when the results array is sorted according to case 1 of the below sorting function. Cases 2 and 3 are the ones that result in no update to the view. This may/may not be necessary information, but the results array gets sorted by these 3 cases in a cycle onClick in the following order: case 1, case 3, case 2.
// in reducer index.js file
case SORT_RESULTS:
return {
...state,
results: sortArr(state.results, state.sortType)
};
// sorting function
function sortArr(arr, sortType) {
let newArr = [];
switch (sortType) {
case '1':
for (let i = arr.length - 1; i >= 0; i--) {
newArr.push(arr[i]);
}
return newArr;
case '2':
newArr = arr;
newArr.sort((a, b) => {
return b.num - a.num;
});
return newArr;
case '3':
newArr = arr;
newArr.sort((a, b) => {
let id1 = a.id.toLowerCase();
let id2 = b.id.toLowerCase();
if (id1 < id2) {
return -1;
}
if (id1 > id2) {
return 1;
}
return 0;
});
return newArr;
default:
return arr;
}
}
The Solution
Here is the culprit: newArr = arr. Change this to newArr = arr.slice(), and the view will get updated on every sort.
Possible Root Cause
As to why this solution works, I invite anyone else's perspective. Here are my thoughts:
According to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice:
"The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included)."
Copying the results array using newArr = arr seems to only copy the pointer (to the array), which is really just a reference in memory. If this statement is true, then Redux would not consider the state to have been changed at all because it is comparing the exact same pointer (state.results), even though the actual data of the array itself is confirmed to change by sorting. Copying a completely new array through slice() would involve a new pointer and data, which would be detected as a change to Redux under the hood.

Pushing element in a cloned object of state adds value to the state attribute instantaneously

I am trying to publish an updated object but not trying to change the state through the following code in react:
addPlaceToSearch = (place) => {
if (place != this.state.search_text) {
var search = $.extend({}, this.state.search);
if (!search.added_places.includes(place)) {
search.added_places.push(place);
PubSub.publish('searchUpdated', search);
}
}
}
Thus, I am using $.extend({}, this.state.search) to clone the object into another variable, modify that particular variable and then publish it. But when I execute the line 5, and I put a breakpoint on line 6, I can see that the this.state.search is also changed with place being pushed in it's added_places key (which I do not want). How is this happening? And how can I prevent it? I have also tried Object.assign({}, this.state.search) instead of $.extend({}, this.state.search).
EDIT
I even tried the most trivial solution, here it is:
addPlaceToSearch = (place) => {
if (place != this.state.search_text) {
if (!this.state.search.added_places.includes(place)) {
var xyz = {};
for (var key in this.state.search) {
xyz[key] = this.state.search[key];
}
xyz.added_places.push(place);
PubSub.publish('searchUpdated', xyz);
}
}
}
Still, the xyz.added_places.push(place) line changes my this.state.search object too! Kindly help me out.
Finally, after two hours of hard work, I figured out the solution by making a deep copy of the original array. I will read about the differences between shallow and deep copy as given in this answer, till then here is the solution:
var xyz = $.extend(true, {}, this.state.search);
xyz.added_places.push(place);
You can do this better without jQuery using Object.assign()
var xyz = Object.assign({}, this.state.search)
xyz.added_places.push(place)

React change state in array (for loop)

I have a State with flights, and I have a slider to change the max price to change visibility of the flight elements.
maxpriceFilter() {
var flightOffer = this.state.flightOffer;
var sliderPrice = this.state.sliderPrice;
for (var i = 0; i < flightOffer.length; i++) {
if( flightOffer[i].price > sliderPrice) {
this.setState(
{[flightOffer[i].hiddenprice] : true}
);
};
}
This code is adding a undefined field with status true in the root of the state though.. I cant find any best practice on this, other then using computed fields. But I cant get the computed field working either..
Could someone please help me out here?
You don't want to do a setState call in a loop, that will have the react component render multiple times. Build a new state object and call the setState once. You also don't want to filter it out by an if statement, but set previous values to hidden:
maxpriceFilter() {
var flightOffer = this.state.flightOffer;
var sliderPrice = this.state.sliderPrice;
var newState = {};
for (var i = 0; i < flightOffer.length; i++) {
newState[flightOffer[i].hiddenprice] = flightOffer[i].price > sliderPrice;
}
this.setState(newState);
// ...
}
If you're still having issues, it could be that the hiddenprice property isn't what you expect? Might need to post your render() function as well.
Instead of doing your looping when you're updating the state, why not just do the switch on render?
You're probably already looping over all flightOffers in render(). So, just add the check there. Inside your render, pass hiddenPrice={offer.price > sliderPrice} as a prop, or use it directly where you control the visibility.
Why? Because the visibility of a specific item in this case is not state. It is a result of the state.

Resources