App purpose: The purpose of this React app is to handle scoring of a very specific dart-game. 2 players, each having to reach 33 hits in the fields 20-13, Tpl's, Dbls's and Bulls. No points, only the number of hits are counted. The hits are added manually by the players (no automatiion required :)). Each targetfield has a row of targets and 2 buttons for adding and removing a hit of that target field.
I have implemented the useContext-design for maintaining state, which looks like this:
export interface IMickeyMouseGameState {
player1 : IPlayer | null,
player2 : IPlayer | null,
winner : IPlayer | null,
TotalRounds : number,
GameStatus: Status
CurrentRound: number
}
Other objects are designed like this :
export interface IGame {
player1?:IPlayer;
player2?:IPlayer;
sets: number;
gameover:boolean;
winner:IPlayer|undefined;
}
export interface IPlayer {
id:number;
name: string;
targets: ITarget[];
wonSets: number;
hitRequirement : number
}
export interface ITarget {
id:number,
value:string,
count:number
}
export interface IHit{
playerid:number,
targetid:number
}
So far so good.
This is the reducer action with the signature:
export interface HitPlayerTarget {
type: ActionType.HitPlayerTarget,
payload:IHit
}
const newTargets = (action.payload.playerid === 1 ? [...state.player1!.targets] : [...state.player2!.targets]);
const hitTarget = newTargets.find(tg => {
return tg.id === action.payload.targetid;
});
if (hitTarget) {
const newTarget = {...hitTarget}
newTarget.count = hitTarget.count-1;
newTargets.splice(newTargets.indexOf(hitTarget),1);
newTargets.push(newTarget);
}
if (action.payload.playerid === 1) {
state.player1!.targets = [...newTargets];
}
if (action.payload.playerid === 2) {
state.player2!.targets = [...newTargets];
}
let newState: IMickeyMouseGameState = {
...state,
player1: {
...state.player1!,
targets: [...state.player1!.targets]
},
player2: {
...state.player2!,
targets: [...state.player2!.targets]
}
}
return newState;
In the Main component i instantiate the useReducerHook:
const MickeyMouse: React.FC = () => {
const [state, dispatch] = useReducer(mickeyMousGameReducer, initialMickeyMouseGameState);
const p1Props: IUserInputProps = {
color: "green",
placeholdertext: "Angiv Grøn spiller/hold",
iconSize: 24,
playerId: 1,
}
const p2Props: IUserInputProps = {
playerId: 2,
color: "red",
placeholdertext: "Angiv Rød spiller/hold",
iconSize: 24,
}
return (
<MickyMouseContext.Provider value={{ state, dispatch }} >
<div className="row mt-3 mb-5">
<h1 className="text-success text-center">Mickey Mouse Game</h1>
</div>
<MickeyMouseGameSettings />
<div className="row justify-content-start">
<div className="col-5">
{state.player1 ?<UserTargetList playerid={1} /> : <UserInput {...p1Props} /> }
</div>
<div className="col-1 bg-dark text-warning rounded border border-warning">
<MickeyMouseLegend />
</div>
<div className="col-5">
{state.player2 ? <UserTargetList playerid={2} /> : <UserInput {...p2Props} /> }
</div>
</div>
</MickyMouseContext.Provider>
);
}
export default MickeyMouse;
Now the reducer-action correctly subtracts 1 from the target's count (the point is to get each target count to 0 and the new state correctly shows the target with 1 less than the old state, but when the Consumer (in this case a tsx-component called UserTargets, which is respnsible for rendering each target with either a circle or an X) the state of the target is 2 lower, even though the reducer only subtracted 1....
After adding a single hit to player 'Peter' in the 20-field - the rendering (with consoloe-logs) looks like this:
So I guess my question is this : Why is the state mutating between the reducer and the consumer and what can I do to fix it?
If further explanation is needed, please ask, if this question should be simplpified, please let me know... I usually don't ask questions here - I mostly find anwers.
The project i available on github: https://github.com/martinmoesby/dart-games