Search code examples
javascriptreactjstypescriptreduxreact-redux

problem in counting times that a function called in typescript


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): the wrong output

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: right output

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)


Solution

  • 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);
      //...