React increment and decrement array counters in state - reactjs

I have some trouble trying to figure out why I can't correctly increment/decrement four counters in an array, which is in the app state. The array in the state is this:
...
constructor(props) {
super(props);
this.state = {
right: 0,
left: 0,
up: 0,
down: 0,
...more variables...
bank: [0, 0, 0, 0] // <- array with 4 counters set to zero
};
}
...
The application fetches JSON data from a Node.js server, and saves the incoming data in the "left", "right", "up" and "down" vars you can see in the state. I select which counter should be incremented/decremented (which I called 'bank') reading the "left" and "right" JSON data, and increment or decrement with "up" and "down". Those signals are working correctly, but the problem is in the logic I think.
This is the function that handles the bank selection (the counter):
bankSelection() {
if (parsedJsonData.right) {
currentBank += 1;
if (currentBank > maxBank) { // restart from zero
currentBank = 0;
}
} else if (parsedJsonData.left) {
currentBank -= 1;
if (currentBank < 0) { // restart from 3
currentBank = 3;
}
}
}
I don't save the bank value in the state, since it shouldn't be rendered. The problem comes with the increment/decrement function:
updateCounters() {
if (parsedJsonData.up) {
if (this.state.bank[currentBank] < 9) {
let bankValue = this.state.bank[currentBank] + 1;
this.setState({ bank: bankValue });
} else {
this.setState({ bank: this.state.bank[currentBank] = 0 });
}
} else if (parsedJsonData.down) {
if (this.state.bank[currentBank] > 0) {
let bankValue = this.state.bank[currentBank] - 1;
this.setState({ bank: bankValue });
} else {
this.setState({ bank: this.state.bank[currentBank] = 9 });
}
}
}
I see the React engine complains on line 7 and 14, saying I shouldn't mutate the state directly, but I'm inside a setState function!
Beside this, when I send a JSON formatted like this:
{
"left": 0,
"right": 0,
"up": 1,
"down": 0
}
the first time the counter in the first bank is updated correctly and shows value 1, but the second time I get this error in the browser's console:
Uncaught (in promise) TypeError: Cannot create property '0' on number '1'
at App.updateCounters (App.js:145:1)
at App.js:112:1
I tryed so many solutions I'm going crazy, any help would me appreciated...
You can find the full code here but bear in mind it's still a work in progress in the render method (the first bank is the only one usable, still testing various solutions).
I hope I gave you all the info to help me fix the error.

I see a few problems with your code.
bank's value is array. You're trying to change it to integer with this.setState({ bank: bankValue })
State should be immutable, if you wish to change the value, you need to create a new array and set the bank state. Something like this:
this.setState({bank: Object.values({...this.state.bank, 1: 100}); // where 1 is the idx where you want to update the value, and 100 is the new value

Finally, I managed to solve the problem.
I managed this using the spread operator and avoiding direct assignment inside the setState method.
updateCounters() {
if (parsedJsonData.up) {
if (this.state.bank[currentBank] < 9) {
// let bankValue = this.state.bank[currentBank] + 1;
var newArrayUp = [...this.state.bank];
newArrayUp[currentBank] += 1;
console.log("array clonato dopo aggiornamento " + [...newArrayUp]);
this.setState({ bank: [...newArrayUp] }, () => {
this.forceUpdate();
});
} else {
var zeroArray = [...this.state.bank];
zeroArray[currentBank] = 0;
console.log(zeroArray[currentBank]);
this.setState({ bank: [...zeroArray] });
this.forceUpdate();
}
} else if (parsedJsonData.down) {
if (this.state.bank[currentBank] > 0) {
var newArrayDown = [...this.state.bank];
newArrayDown[currentBank] -= 1;
console.log("array clonato dopo aggiornamento " + [...newArrayDown]);
this.setState({ bank: [...newArrayDown] }, () => this.forceUpdate());
} else {
var nineArray = [...this.state.bank];
nineArray[currentBank] = 9;
console.log(nineArray[currentBank]);
this.setState({ bank: [...nineArray] }, () => this.forceUpdate());
}
}
}
Now I create a copy of the original array, work on it and then pass it to the setState method.
Thanks for suggestions!

Related

Adding and removing tracks from a Spotify playlist app

I'm currently completing a project where I have to build a Spotify playlist creator. For part of this, I need to add or remove tracks from the playlist. I've coded a method that appears to work, but it is different from the official solution, so I just want to see whether there is a reason I shouldn't do it my way.
Specifically, they use .find and .filter methods where I have used .includes. Is there a downside to what I've done?
Their code
addTrack(track) {
let tracks = this.state.playlistTracks;
if (tracks.find(savedTrack => savedTrack.id === track.id)) {
return;
}
tracks.push(track);
this.setState({ playlistTracks: tracks});
}
removeTrack(track) {
let tracks = this.state.playlistTracks;
tracks = tracks.filter(currentTrack => currentTrack.id !== track.id);
this.setState({playlistTracks: tracks});
}
My code
addTrack(track) {
let tracks = this.state.playlistTracks;
if (!tracks.includes(track)) {
tracks.push(track);
}
this.setState({playlistTracks: tracks});
}
removeTrack(track) {
let tracks = this.state.playlistTracks;
if (tracks.includes(track)) {
let index = tracks.indexOf(track);
tracks.splice(index, 1);
}
this.setState({playlistTracks: tracks});
}
Yes, there is a significant difference, because includes() will only return true if you pass it the actual instance (by that I mean a reference that points to the same object) of track that you are looking for.
The provided solution compares tracks only based on the track ID, so that is something different.
See the following example:
const tracks = [
{id: 1, title: "mysong1"},
{id: 2, title: "mysong2"},
]
function isTrackPresentIncludes(track){
return tracks.includes(track);
}
function isTrackPresentFind(track){
return tracks.find(it => it.id === track.id) !== undefined;
}
// this will be true
console.log("with includes(tracks[0]):\n", isTrackPresentIncludes(tracks[0]))
// this will be false as it's a different object
console.log("with includes({id: 1, title: \"mysong1\"}):\n", isTrackPresentIncludes({id: 1, title: "mysong1"}))
// this will be true
console.log("with find(tracks[0]):\n", isTrackPresentFind(tracks[0]))
// this will also be true
console.log("with find({id: 1, title: \"mysong1\"}):\n", isTrackPresentFind({id: 1, title: "mysong1"}))
You have the same issue with indexOf() in your removeTrack().
There is another thing I don't particularly like about the solution. find() returns the track that was found but that return value is never actually used so to my mind you should use some() instead which just returns true or false.
I don't think this is a problem here but it could potentially lead to unexpected behavior if an array would hold falsy values.
Consider this:
const arrayWithFalsyValues = [
0, // zero is falsy!
1,
2,
3,
4,
5,
6,
7,
8
]
function isPresent(toBeFound){
if(arrayWithFalsyValues.find(number => number === toBeFound)){
console.log(`Value ${toBeFound} found in array`);
}
else{
console.log(`Value ${toBeFound} NOT found in array`);
}
}
console.log("Array:", arrayWithFalsyValues)
// this will work as expected
console.log("Contains 3?")
isPresent(3)
console.log("Contains 8?")
isPresent(8)
console.log("Contains 10?")
isPresent(10)
// now search for the falsy value -> incorrect result
console.log("Contains 0?")
isPresent(0)
Issue is with referencing, You have make another reference of playlistTracks
addTrack(track) {
let { playlistTracks } = this.state;
let tracks = [...playlistTracks];
if (!tracks.includes(track)) {
tracks.push(track);
}
this.setState({ playlistTracks: tracks });
}
removeTrack(track) {
let { playlistTracks } = this.state;
let tracks = [...playlistTracks];
if (tracks.includes(track)) {
let index = tracks.indexOf(track);
tracks.splice(index, 1);
}
this.setState({ playlistTracks: tracks });
}
MY SUGGESTION
addTrack(track) {
const { playlistTracks } = this.state;
const tracks = [...playlistTracks];
const index = tracks.indexOf(track);
if (index < 0) {
tracks.push(track);
}
this.setState({ playlistTracks: tracks });
}
removeTrack(track) {
const { playlistTracks } = this.state;
const tracks = [...playlistTracks];
const index = tracks.indexOf(track);
if (index > -1) {
tracks.splice(index, 1);
}
this.setState({ playlistTracks: tracks });
}

React native: Why am I getting an 'Uncaught Error: Maximum call stack size exceeded'?

I've written the code below (please be noted that some unrelated functions have been removed).
I'm trying to make a Player-vs-Computer TicTacToe game. But every time I try to run the code on Android Studio, Uncaught error: Maximum call stack size exceeded will be shown, specifically when it's Computer's turn to make a move. I think the error is in the onTilePress function, but I'm not sure how to solve it. Does anyone have an idea what is wrong there? Any suggestion will be much appreciated. Thank you in advance.
/* eslint-disable prettier/prettier */
...
export default class TicTacToe2 extends React.Component {
constructor(props){
super(props);
this.state = {
gameState: [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
] ,
currentPlayer: 1,
}
//tilesSelected : 0;
}
componentDidMount(){ //To start the first render... I guess
this.initializeGame();
}
//Reset the gameboard
initializeGame = () => {
this.setState({gameState:
[
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
],
currentPlayer: 1,
});
}
checkTie = () => {
...
}
//To check if the tile selected is owned
checkTilesOwner = (row, col) => {
var arr = this.state.gameState;
if (arr[row][col] == 1){ //Tile owned by Player 1
//Alert.alert("Owned by player 1!"); //remove comment for trial-and-error
this.BotMove();
}
else if (arr[row][col] == -1) { //Tile owned by Bot
//Alert.alert("Owned by Bot!"); //remove comment for trial-and-error
this.BotMove();
}
else { //Empty tile
this.onTilePress(row, col); //Select the tile
}
}
//Return 1 if player 1 won, -1 if player 2 / bot won, or 0 if no one has won
getWinner = () => {
...
}
BotMove = () => {
var RandRow = Math.floor(Math.random() * 3) + 0; //Generate random number for Row
var RandCol = Math.floor(Math.random() * 3) + 0; //Generate random number for Col
this.checkTilesOwner(RandRow, RandCol);
}
onTilePress = (row, col) => {
//Dont allow tiles to change
var value = this.state.gameState[row][col];
if (value !== 0) { return;}
//Identify and grab current player
var currentPlayer = this.state.currentPlayer;
//Set the correct tile...
var arr = this.state.gameState.slice();
arr[row][col] = currentPlayer;
this.setState({gameState: arr});
//Switch to other player
var nextPlayer = (currentPlayer == 1) ? -1 : 1; //if yes, then change to -1; else, then change to 1
this.setState({currentPlayer: nextPlayer});
//Alert.alert(nextPlayer);
//Check if the match is tie
var checkDraw = this.checkTie();
//check winner
var winner = this.getWinner(); //get the winner update
if (winner == 1) {
Alert.alert("Player 1 has won!");
this.initializeGame();
}
else if (winner == -1){
Alert.alert("Bot has won!");
this.initializeGame();
}
else if (checkDraw == 9){
Alert.alert("It's a draw!");
this.initializeGame();
}
//Identify the current player: 1 is human, -1 is bot
var takePlayer = '' + nextPlayer;
if (takePlayer == 1) {
Alert.alert("Player 1's turn!");
}
else if (takePlayer == -1) { //If it is Bot's turn to nmake a move, then trigger BotMove
Alert.alert("Bot's turn!");
this.BotMove(); //Uncaught error: Maximum call stack size exceeded //possible cause: endless loop
}
}
onNewGamePress = () => {
this.initializeGame();
}
renderIcon = (row, col) => {
var value = this.state.gameState[row][col];
switch(value) {
case 1:
return <TouchableOpacity><Text style={styles.tileX}>X</Text></TouchableOpacity>;
case -1:
return <TouchableOpacity><Text style={styles.tileO}>O</Text></TouchableOpacity>;
default:
return <View />
}
}
render() {
return (
//Container for Gameboard
...
);
}
}
Yes, your BotMove and checkTilesOwner are circular.
Try to simplify your logic into
onTilePress(...) {
// think what needs to be done
// get only stuff you need without moving it
this.BotMove(...)
}
Don't blend the thinking code (logic) with the action itself. Because otherwise you will end up with lots of this.setState call. Not only this could cause lots of circular problem, but only it won't help you understand your business logic, in your case, your game logic :)
Separate your logic from your rendering.

Dynamic # of states using for loop and setState . Then need to use states after all have been set

Dynamic number of states. The number of states are dynamic and depends on this mppt number which depends on the user input. I create the states and then after they are all created in the for loop... I'd like to do something with it. I would normally do it in the callback, but I'm using a for loop to create the dynamic number of states. This sets up the defaults and number of elements depending on mppt number. Afterwards, it will be updated by an onChange.
const defaultValues = {
1:{
isc: 10.88,
power: 3834.5999999999995,
vmp: 497,
voc: 584.3312579999999,
se: 14,
st: 1,
},
2: {isc: 9.00,
power: 3834.5999999999995,
vmp: 600,
voc: 584.3312579999999,
se: 12,
st: 1},
}
const mppt = 2
.
.
.
componentDidMount(){
console.log('Sizing Mounted?')
for(let i=1 ; i <= mppt ; i++){
console.log(i)
this.setState({ [`${i}`] : defaultValues[i]}, ()=>{
console.log('state been set?')
console.log(this.state[`${i}`]) //this works
})
}
console.log('Check if state has been checked by here to feed into next function')
console.log(this.state[`1`]) // undefined
//This function uses the new set states
this.createElementDefaults() //uses undefined state values
}
The asyncronous nature of setStates is messing with this. Any suggestions?
Try building the object first, set it into the state and then use your funciton in the callback:
componentDidMount() {
let s = {};
for (let i = 1; i <= mppt; i++) {
s[`${i}`] = defaultValues[i];
}
this.setState((prevState) => ({ ...s }), () => {
console.log(this.state);
this.createElementDefaults()
});
}

Generating non duplicate items in componentDidMount using Redux

I am creating a multipage application. When the user navigates to one of my pages componentDidMount triggers and currently generates 4 random numbers within a range I have specified and sends them through my reducer. This eventually comes back in the form of an array.
Within componentDidMount I have a loop to call my generateRandomNumber function four times. However, if I go to log the output array I get a blank array, which is the initial state for said reducer.
Logging the array in componentDidUpdate, or anywhere after the mount returns what I want it to but that doesn't help me in checking for duplicates as I cannot check for duplicates if I am checking against a blank array. Everything should be without delay, but there seems to be an issue with trying to read from this.props.currentUserList from within componentDidMount.
I need to verify that I am not adding two of the same user into my array, but I need the pre-check to all be done by the time this appears on-screen.
//gives me a random user from my array
generateRandomUser() {
return myArray[Math.floor(Math.random() * 100) + 1]
}
//sends 4 randomUsers through my actions to my reducers
generateNewID() {
let amountOfOptions = 4;
while (amountOfOptions > 0) {
let randomUser = this.generateRandomUser();
if (this.props.currentUserList.length === 0) {
this.props.setCurrentUser(randomUser);
} else {
//this never fires as currentUserList is somehow still == []
this.checkForDuplicates();
}
amountOfOptions--;
}
}
componentDidMount() {
this.generateNewID()
console.log(this.props.currentUserList)
//returns []
}
componentDidUpdate() {
console.log(this.props.currentUserList)
// returns [
// user2: {
// name: 'name'
// },
// user4: {
// name: 'name'
// },
// user28: {
// name: 'name'
// },
// user92: {
// name: 'name'
// },
// ]
}
I am not sure about the implementation of the array. However if the required output is to get 4 random users then instead of of calling setcurrentuser 4 times, you can generate 4 users and then set them at once.
generateNewID() {
let amountOfOptions = 4;
let randomUserList = [];
while (amountOfOptions > 0) {
let randomUser = this.generateRandomUser();
if (randomUserList.length === 0) {
randomUserList.push(randomUser);
} else {
//this never fires as currentUserList is somehow still == []
this.checkForDuplicates();
}
amountOfOptions--;
}
this.setCurrentUser(randomUserList);
}

Increment the ID for every addition of elements in React

i am having a problem where i cannot able to increment the ID of the state element for every addition of elements in the row. What i am getting is same number is repeating for every addition of elements, i need something like ID should be 1 to n numbers of addition.
In text-box, i am not entering the ID, i enter only the LText(Name).
In general, the ID should generate for every addition of elements.
What i have tried is..
export default class EPIM091 extends cntrl.WITBase {
constructor(props) {
super(props);
const witState = this.state;
if (Object.keys(witState).length == 0) {
witState.model = { LID: 1, LText: '', SOrder: '', Inventoried: false, Location:[] }; //Here i need to store all other state values to the Location Array
}
}
clickAction = (e) => {
if (e.id == 'btn_add') {
//var i = 1;
const { model } = this.state;
if (model.LText != null) {
if ((model.Location || []).length == 0) {
model.Location = [];
}
// this.setState(prevState => { return { LID: e.id == 'btn_add' ? prevState.LID + 1 : prevState.LID - 1 } }); //This does not worked
model.Location.push({ "LID": model.LID.toString(), "LText": model.LText, "SOrder": model.SOrder, "Inventoried": model.Inventoried.toString() });
this.setState({
model: model,
LID: model.LID + 1 //This also not worked
});
}
}
};
render(){
// Due to confusion of code, i did not add the textboxes codes
<cntrl.WITButton id="btn_add" onWitClick={this.clickAction} />
}
I need something like when i add, i should get the Unique LID from 1 to the number of elements added. What i am getting is same ID i.e 1. Thank you
Many problems here :
1) You are trying to modify a variable that you declared constant:
const witState = this.state;
if (Object.keys(witState).length == 0) {
witState.model = { LID: 1, LText: '', SOrder: '', Inventoried: false, Location:[] };
2) You are trying to access state variable, but you never define it:
const { model } = this.state;
3) You try to modify it even if it's declared constant:
if (model.LText != null) {
if ((model.Location || []).length == 0) {
model.Location = [];
}
// this.setState(prevState => { return { LID: e.id == 'btn_add' ? prevState.LID + 1 : prevState.LID - 1 } }); //This does not worked
model.Location.push({ "LID": model.LID.toString(), "LText": model.LText, "SOrder": model.SOrder, "Inventoried": model.Inventoried.toString() });
this.setState({
model: model,
LID: model.LID + 1 //This also not worked
});
}
Unless there is a lot of code you didn't show us, you must have a lot of errors in your console. Watch it.
-[Edit]-:
The problem is that I can't really help you unless I have the whole code, because as it is, it doesn't make sense. If you just want to increment your LID state var, here is a working example:
constructor(props) {
super(props);
this.state={LID:1}
}
test=(e)=>{
let {LID} = this.state;
this.setState({LID:LID+1})
console.log(this.state.LID);
}
render(){
return(
<button onClick={this.test}> Test</button>
)
}

Resources