I'm a very beginner in react and redux and I'm working on a tic tac toe project using this two.
Recently I ran to a new problem which is my function count++ does not work correctly.
I'll put the whole code in the end of the question, but this is a summary of the code which has the problem.
// my function which I want to count the times it's called by onClick event
let count = 0
const handleTurn = (i: number, j: number) => {
// this is the counter that doesn't work correctly
count ++
console.log(count)
const newTurn = {
i: i,
j: j
};
const newGameStatus = {
message: status
}
if (winner === null) {
dispatch(turn(newTurn));
dispatch(gameStatus(newGameStatus))
}
}
// buttons that are calling my function
<table>
<tbody>
{states?.table.map((row, index) => {
return (
<tr key={index}>
{row[0] === "-" ? (
<td className="paper-btn" onClick={() => { handleTurn(index, 0) }}>{row[0]}</td>
) : (
<td className="paper-btn">{row[0]}</td>
)}
{row[1] === "-" ? (
<td className="paper-btn" onClick={() => { handleTurn(index, 1) }}>{row[1]}</td>
) : (
<td className="paper-btn">{row[1]}</td>
)}
{row[2] === "-" ? (
<td className="paper-btn" onClick={() => { handleTurn(index, 2) }}>{row[2]}</td>
) : (
<td className="paper-btn">{row[2]}</td>
)}
</tr>
)
})}
</tbody>
</table>
and the output will be like this (highlighted in red):
But when I deleted all the content of my function it worked correcly
let count = 0
const handleTurn = (i: number, j: number) => {
count ++
console.log(count)
}
and the output will be correct:
I want to have all the content in my function but also the counter works correct, and I don't what's the problem with my function or code.
also my whole code (including the problem part):
// store.ts
import { configureStore } from '@reduxjs/toolkit';
import tictactoeReducer from '../components/ticTacToeSlice';
import storage from 'redux-persist/lib/storage';
import { persistReducer, persistStore } from 'redux-persist';
import thunk from 'redux-thunk';
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, tictactoeReducer)
export const store = configureStore({
reducer: {
persistedReducer,
},
devTools: process.env.NODE_ENV !== 'production',
middleware: [thunk]
});
export const persistor = persistStore(store)
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
// hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// ticTacToeSlice.ts
import { createSelector, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "../app/store";
type Cell = 'X' | 'O' | '-'
type Table = Array<Array<Cell>>
type Turn = Exclude<Cell, '-'>
export interface State {
move: string[]
table: Table
turn: Turn
gameStatus: string
}
const initialState: State = {
move: [],
table: [
['-', '-', '-'],
['-', '-', '-'],
['-', '-', '-']
],
turn: "X",
gameStatus: "choose a box to start"
}
interface TurnAction {
i: number,
j: number,
}
interface GameStatusAction {
message: string
}
interface moveAction {
index: string[]
}
export const tictactoeSlice = createSlice({
name: "tactactoe",
initialState,
reducers: {
turn: (state, action: PayloadAction<TurnAction>) => {
state.table[action.payload.i][action.payload.j] = state.turn
state.turn = state.turn === 'O' ? 'X' : 'O'
},
move: (state, action: PayloadAction<moveAction>) => {
state.move = action.payload.index
},
gameStatus: (state, action: PayloadAction<GameStatusAction>) => {
state.gameStatus = action.payload.message
},
reset: () => initialState
},
});
function calculateWinner(squares: Cell[]) {
const lines = [
// a, b, c
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] !== '-' && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
export const selectSquares = (state: RootState) => state.persistedReducer.table
export const selectWinner = createSelector(
selectSquares,
(squares) => {
const squaresArray = squares.flat();
return calculateWinner(squaresArray);
}
);
export const { turn, reset, move, gameStatus } = tictactoeSlice.actions;
export const turnSelector = (state: RootState) => state.persistedReducer;
export default tictactoeSlice.reducer;
// ticTacToe.tsx
import { useEffect, useState } from "react";
import { useAppDispatch, useAppSelector } from "../app/hooks";
import { State, reset, selectWinner, gameStatus, turn, turnSelector } from "./ticTacToeSlice";
function TicTacToe() {
const [states, setStates] = useState<State>();
const selectedTurn = useAppSelector(turnSelector);
const dispatch = useAppDispatch();
const status = "game is on"
useEffect(() => {
setStates(selectedTurn);
}, [selectedTurn]);
let count = 0
const handleTurn = (i: number, j: number) => {
count ++
console.log(count)
const newTurn = {
i: i,
j: j
};
const newGameStatus = {
message: status
}
if (winner === null) {
dispatch(turn(newTurn));
dispatch(gameStatus(newGameStatus))
}
}
const winner = useAppSelector(selectWinner);
return (
<div className="container">
<div>
{winner === null ? (
<div>
{states?.turn === "X" ? (
<h3>current turn: <span className="text-secondary">{states?.turn}</span></h3>
) : (
<h3>current turn: <span className="text-danger">{states?.turn}</span></h3>
)}
<h3>{states?.gameStatus}</h3>
</div>
) : (
<h3>winner is <span className="text-success">{winner}</span></h3>
)}
</div>
<table>
<tbody>
{states?.table.map((row, index) => {
return (
<tr key={index}>
{row[0] === "-" ? (
<td className="paper-btn" onClick={() => { handleTurn(index, 0) }}>{row[0]}</td>
) : (
<td className="paper-btn">{row[0]}</td>
)}
{row[1] === "-" ? (
<td className="paper-btn" onClick={() => { handleTurn(index, 1) }}>{row[1]}</td>
) : (
<td className="paper-btn">{row[1]}</td>
)}
{row[2] === "-" ? (
<td className="paper-btn" onClick={() => { handleTurn(index, 2) }}>{row[2]}</td>
) : (
<td className="paper-btn">{row[2]}</td>
)}
</tr>
)
})}
</tbody>
</table>
<br />
<input type="button" className="paper-btn btn-primary-outline" value="Reset" onClick={() => dispatch(reset())} />
</div>
);
}
export default TicTacToe;
/* settings.css */
.disabled-btn {
pointer-events: none;
}
(sorry if I had grammer mistakes)
Because on every render the component does this:
let count = 0
So count
is declared and initialized to 0
every time. Contrast this with a value that is successfully persisting across multiple renders:
const [states, setStates] = useState<State>();
The difference is the use of state. To maintain the value of count
across renders, put it in state:
const [count, setCount] = useState(0);
Then instead of directly incrementing count
, update its state:
const handleTurn = (i: number, j: number) => {
setCount(count + 1);
console.log(count + 1);
//...