I am building a two player game in react/redux. There is always one player whose turn it is. The turn needs to change regularly between the two.
I have the following reducer. It works once, and then stops working.
case CHANGE_TURN:
if (playerTurn === 1) {
newPlayerTurn = 2;
newOtherPlayer = 1;
}
else {
newPlayerTurn = 1;
newOtherPlayer = 2;
}
return {
...state,
otherPlayerID: newOtherPlayer,
playerTurnID: newPlayerTurn
}
Since this part has worked in the past, I think perhaps something else is getting in the way????
I think the answer was accepted but you can also minimize your swapping operation by using:
[newPlayerTurn, newOtherPlayer] = [newOtherPlayer, newPlayerTurn];
Cloned your repo and checked it out.
Your players.js reducer, line 5 is:
let playerTurn = players.playerTurnID;
But should look like:
let playerTurn = state.playerTurnID;
You should get your playerTurn from your state object.
You might have forgotten to alternate playerTurn between 1 and 2 by returning it in the state. Also, the case can be simplified:
case CHANGE_TURN:
return {
...state,
playerTurn: state.playerTurn === 1 ? 2 : 1,
otherPlayerID: state.playerTurn === 1 ? 1 : 2,
playerTurnID: state.playerTurn === 1 ? 2 : 1,
};
playerTurn and playerTurnID are redundant btw, but maybe your example was simplified.
If it's strictly always exactly two players, one boolean is enough to model the entire state:
case CHANGE_TURN:
return { activePlayer: !state.activePlayer };
With an initialState of { activePlayer: false }. You could then write selectors like
const isPlayer1Active = (state) => !state.activePlayer; // false = player1's turn
const isPlayer2Active = (state) => state.activePlayer; // true = player2's turn
const getActivePlayerID = (state) => state.activePlayer ? 2 : 1;
Related
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!
I have data from an movie-api I want to sort based on a select menu, either by year in descending order or title in alphabetical order.
Although Im only updating state in the sort function, not using the variable any where, the data thats already mapped out, and in a different array, updates accordingly. I guess its somehow related to the first change in state for each of the two differen variables?
Any idea how I should solve this correctly and why this is happening?
const sortData = (e) => {
if (e === "year"){
const yearData = data.sort(function(a, b) {
const yearA = a.Year;
const yearB = b.Year;
if (yearA < yearB) {
return -1;
}
if (yearA > yearB) {
return 1;
}
return 0;
});
setYearData(yearData);
}
if (e === "title") {
const titleData = data.sort(function(a, b) {
const titleA = a.Title.toUpperCase();
const titleB = b.Title.toUpperCase();
if (titleA < titleB) {
return -1;
}
if (titleA > titleB) {
return 1;
}
return 0;
});
setTitleData(titleData);
}
}
The sort() method sorts the elements of an array in place, so the data(state) changed without using setState (It may cause some unpredictability happened in the execution)
You can use the sort() method on a copy of the array, so it doesn't affect your state array, I guess this change may work:
use [...data].sort(...) instead of data.sort(...)
Array.sort(), in your case data.sort() updates the original array in addition to returning it. Seems like your data variable is some sort of global that gets changed during sort().
I'm writing a small app that uses the react-sortable-hoc
everything is great but im having issues displaying the list ordered by order
I have
user 0
user 1
user 2
when I drag user 2 above user 0
instead of getting
user 2
user 0
user 1
I get
user 2
user 1
user 0
I think It has to do with the way I'm setting the order in the state. but I can't figure it out.
this is how I set the order on sort end
const onSortEnd = ({ oldIndex, newIndex }) => {
setUsers(prevState => {
const newItems = [...prevState];
newItems[newIndex].order = oldIndex;
newItems[oldIndex].order = newIndex;
return newItems.sort((a, b) => a.order - b.order);
})
};
here's the app running so you can play with it.
https://codesandbox.io/s/winter-https-xelrd?fontsize=14&hidenavigation=1&theme=dark
I have fixed it,
here is the working url to play with https://codesandbox.io/s/quizzical-colden-rm62y
You were correct in guessing that the problem was with the onSortEnd function. Instead of swapping the newIndex and oldIndex position we just need to either bubble them up or down.
Here is a working code, it can be cleaned up a bit, but you got the idea :)
const onSortEnd = ({ oldIndex, newIndex }) => {
setUsers(prevState => {
const newItems = [...prevState];
if (oldIndex > newIndex) {
for (let i = oldIndex - 1; i >= newIndex; i--) {
newItems[i].order++;
newItems[oldIndex].order = newIndex;
}
} else if (oldIndex < newIndex) {
for (let i = oldIndex + 1; i <= newIndex; i++) {
newItems[i].order--;
newItems[oldIndex].order = newIndex;
}
}
return newItems.sort((a, b) => a.order - b.order);
});
};
Hope it helps. Happy coding :)
What you do is swapping.
If you want to just "insert" the element in the new position you will have to update all the items between the two positions.
In your case, one approach would be to just move the element and re-create the order for all items
setUsers(prevState => {
const newItems = [...prevState];
newItems.splice(newIndex, 0, newItems.splice(oldIndex, 1)[0]).forEach((item,index)=>{
item.order = index;
});
return newItems
});
Demo at https://codesandbox.io/s/confident-river-mrh3p
So looks like your code is simply swapping the elements. This does not seem like what you really want to do. In fact you really want to remove the element and insert it at a given position. I think since you already have the oldIndex and newIndex, you can approach the sort function as follows:
const onSortEnd = ({ oldIndex, newIndex }) => {
setUsers(prevState => {
var newItems = [...prevState];
let elem = newItems[oldIndex]
newItems.splice(oldIndex, 1)
newItems.splice(newIndex, 0, elem)
return newItems
});
};
There isn't really a need for order and is capturing more than the minimum state required (unless you use it elsewhere).
While reading the documentation about updating nested state object in Redux, I stumbled upon this common mistake, which states that doing a shallow copy of the top level object is not sufficient:
function updateNestedState(state, action) {
// Problem: this only does a shallow copy!
let newState = {...state};
// ERROR: nestedState is still the same object!
newState.nestedState.nestedField = action.data;
return newState;
}
but I couldn't find out why it isn't sufficient, because technically it's working, As you can see in this fiddle:
https://codesandbox.io/s/D9l93OpDB
I'll be happy for further clarification/explanation regarding this statement, and also will be happy for an example of what is the best practice of updating such state objects.
Thanks!
Assume you have a state object like that:
let state = { a: { b: 1 } };
let cp = { ...state }
cp.a === state.a //true, that means that those are the exact same objects.
that is because the »inner« Object (state.a) is added to cp by reference. If you now do:
cp.a.b = 10;
You change the value also in state.a.b. So the mistake here is: if you want to modify state.a.b, than you have to replace that with a new Object, having a new value like so:
let cp = {
...state,
a: {
...state.a.b
b: 2
}
}
The reason for this is that you are asked to write »pure« functions, to explain consider this:
var a = { b: 1 };
//not a pure function
function foo (obj) {
obj.b++;
return obj;
}
for (var i = 0; i < 10; i++) foo(a);
//a.b = 11;
So the object is modified each call of foo(a) will produce different output and modify a global variable.
The above can lead you to really nasty bugs, which are hard to find and to prevent you from running into this prefer the below.
//a pure function
function bar (obj) {
return {
...obj,
b + 1
}
}
for (var i = 0; i < 10; i++) bar(a);
//a.b = 2
In that case a is not modified, so the output of bar(a) will always yield the same output.
I am kinda bumped because it used to work automatically when you are using connect api from react-redux.
But now I am facing a situation where some data is not getting updated when something gets returned from reducer..
this is my pseudo code..
in the reducer file I have..
case "SORT_BY_DATE":
let startDateSort = state.startDateSort;
let endDateSort = state.endDateSort;
const sortByStartDateUp = function(a, b) {
return new Date(a.start_date) > new Date(b.start_date) ? 1 :-1;
}
const sortByStartDateDown = function(a, b) {
return new Date(b.start_date) > new Date(a.start_date) ? 1:-1;
}
const sortByEndDateUp = function(a, b) {
return new Date(a.stop_date) > new Date(b.stop_date) ? 1 : -1;
}
const sortByEndDateDown = function(a, b) {
return new Date(b.stop_date) > new Date(a.stop_date) ? 1 : -1;
}
let sortFunctionByDate = "";
if (action.time == "start"){
sortFunctionByDate = startDateSort == true ? sortByStartDateUp : sortByStartDateDown;
startDateSort = !startDateSort;
}else{
sortFunctionByDate = endDateSort == true ? sortByEndDateUp : sortByEndDateDown;
endDateSort = !endDateSort;
}
let filtered = state.status + "Banners";
let banners_filtered =state['banners'].sort(sortFunctionByDate)
state[filtered] = banners_filtered;
state["banners"] = banners_filtered
return Object.assign({}, state, {
startDateSort,
endDateSort,
showImageButton: false,
})
Up to here I was able to assert that I am getting correct info. from the banners_filtered variable.
this is my container component:
const mapStateToProps = (state) => {
let {liveBanners} = state.BannerReducer;
console.log("liveBanners", liveBanners)
return {
liveBanners
}
}
const BannerTableList = connect(mapStateToProps,mapDispatchToProps(BannerTable)
----I was able to log liveBanners here..as well---
But this value is not getting updated in the dumb compononent..like other variables I have been doing...Could it be because there is too much? computation inside the reducer? I hardly doubt that is the reason. But if you have any hunch what could have gone wrong please let me know. I am all ears at this point.. thanks.
--also there is no error shown in the console ---
---more code update--
const BannerTable = ({liveBanners}) => {
console.log("banners", liveBanners)
}
above is my dumb component using stateless function and liveBanners never logged in the console.
You're mutating your current state object in your reducer here:
state[filtered] = banners_filtered;
state["banners"] = banners_filtered
return Object.assign({}, state, {
startDateSort,
endDateSort,
showImageButton: false,
})
This can prevent react-redux from detecting changes, which will prevent expected rerenders. You probably just need to change this to:
return Object.assign({}, state, {
[filtered]:banners_filtered,
banners: banners_filtered,
startDateSort,
endDateSort,
showImageButton: false,
})