react.js – I’m new to redux and wonder if I use it the correct way

It’s a cards war game. I’m new to redux and wonder if I use it the correct way, especially when I declare actions in the Game and Main components, and use action’s payloads as callbacks to update the state. It feels like a lot code for a small app. Maybe you can help guys and give me some insights, thanks.

store.js:

import { createStore } from "redux";

const state = {
  game_ready: false,
  cards: (),
  player: { name: "", cards: (), points: 0 },
  computer: { name: "computer", cards: (), points: 0 },
};

const reducer = (state, action) => {
  switch (action.type) {
    case "INIT_GAME_CARDS":
      return action.payload(state);
    case "UPDATE_PLAYER_NAME":
      return action.payload(state);
    case "SET_GAME_READY":
      return action.payload(state);
    case "DIST_CARDS":
      return action.payload(state);
    case "SET_NEXT_CARDS":
      return action.payload(state);
    case "INCREASE_POINTS":
      return action.payload(state);
    case "RESET_GAME":
      return action.payload(state);
    default:
      return state;
  }
};

const store = createStore(reducer, state);

export default store;

Main.js:

import React, { useEffect } from "react";
import { Button } from "@material-ui/core";
import { useHistory } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { NUM_OF_CARDS, MAX_CARD_VALUE } from "./constants";
import { shuffle } from "./helpers";

// action creator to initialize the game
const initGameCards = () => ({
  type: "INIT_GAME_CARDS",
  payload: (state) => {
    // creates an array of size 52 filled with 1..13 four times
    const cards = Array(NUM_OF_CARDS / MAX_CARD_VALUE)
      .fill(
        Array(13)
          .fill()
          .map((_, i) => i + 1)
      )
      .flat();
    // shuffle the cards
    shuffle(cards);
    return {
      ...state,
      cards,
    };
  },
});

// action creator to control the player's name
const updatePlayerName = (name) => ({
  type: "UPDATE_PLAYER_NAME",
  payload: (state) => ({
    ...state,
    player: { ...state.player, name: name },
  }),
});

const setGameReady = () => ({
  type: "SET_GAME_READY",
  payload: (state) => ({
    ...state,
    game_ready: true,
  }),
});

function Main() {
  const history = useHistory();
  const dispatch = useDispatch();
  const player = useSelector(({ player }) => player);
  // const game_ready = useSelector(({ game_ready }) => game_ready);

  const handleClick = React.useCallback(
    (e) => {
      e.preventDefault();
      if (player.name) {
        dispatch(setGameReady());
        history.replace("./game");
      }
    },
    (dispatch, player.name)
  );

  useEffect(() => {
    dispatch(initGameCards());
  }, ());

  const handleChange = React.useCallback((e) => {
    const target = e.target;
    const val = target.value;
    switch (target.id) {
      case "playerName":
        dispatch(updatePlayerName(val));
        break;
      default:
        break;
    }
  });

  return (
    <div>
      {/* check for valid input */}
      <form>
        <label htmlFor="playerName">
          <h1 className="text-blue-800 text-5xl text-shadow-lg mb-3">
            Ready for war
          </h1>
        </label>
        <input
          className="border focus:ring-2 focus:outline-none"
          id="playerName"
          required
          onChange={handleChange}
          placeholder="Enter your name"
          type="text"
          value={player.name}
        />
        {!player.name ? (
          <p className="text-red-700">Please fill the field</p>
        ) : (
          ""
        )}
        <Button
          onClick={handleClick}
          type="submit"
          color="primary"
          variant="contained"
        >
          Start
        </Button>
      </form>
    </div>
  );
}

export default Main;

Game.js:

import { Button } from "@material-ui/core";
import React from "react";
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useHistory } from "react-router-dom";
import { NUM_OF_CARDS } from "./constants";
import { shuffle } from "./helpers";

// action creator to distribute the cards at the beginning of the game
const distCards = () => ({
  type: "DIST_CARDS",
  payload: (state) => {
    const cards = (...state.cards);
    shuffle(cards);
    const computer_cards = cards.slice(0, NUM_OF_CARDS / 2);
    const player_cards = cards.slice(NUM_OF_CARDS / 2);
    const computer_current_card = computer_cards.pop();
    const player_current_card = player_cards.pop();
    return {
      ...state,
      cards,
      // distributes cards evenly
      computer: {
        ...state.computer,
        cards: computer_cards,
        current_card: computer_current_card,
        points: 0,
      },
      player: {
        ...state.player,
        cards: player_cards,
        current_card: player_current_card,
        points: 0,
      },
    };
  },
});

const setNextCards = () => ({
  type: "SET_NEXT_CARDS",
  payload: (state) => {
    let (computer_cards, player_cards) = (
      (...state.computer.cards),
      (...state.player.cards),
    );
    const (computer_next_card, player_next_card) = (
      computer_cards.pop(),
      player_cards.pop(),
    );
    return {
      ...state,
      player: {
        ...state.player,
        cards: player_cards,
        current_card: player_next_card,
      },
      computer: {
        ...state.computer,
        cards: computer_cards,
        current_card: computer_next_card,
      },
    };
  },
});

const pointsIncreament = () => ({
  type: "INCREASE_POINTS",
  payload: (state) => {
    const (player_current_card, computer_current_card) = (
      state.player.current_card,
      state.computer.current_card,
    );
    return {
      ...state,
      player: {
        ...state.player,
        points:
          player_current_card > computer_current_card
            ? state.player.points + 1
            : state.player.points,
      },
      computer: {
        ...state.computer,
        points:
          player_current_card < computer_current_card
            ? state.computer.points + 1
            : state.computer.points,
      },
    };
  },
});

function Game() {
  const player = useSelector(({ player }) => player);
  const computer = useSelector(({ computer }) => computer);
  const game_ready = useSelector(({ game_ready }) => game_ready);
  const dispatch = useDispatch();
  const history = useHistory();
  const handleReset = React.useCallback(() => {
    dispatch(distCards());
  }, (dispatch));

  useEffect(() => {
    if (game_ready) {
      dispatch(distCards());
    } else {
      history.replace("/");
    }
  }, (game_ready));

  useEffect(() => {
    if (player.current_card && computer.current_card) {
      dispatch(pointsIncreament());
    }
  }, (player.current_card, computer.current_card));

  const handleClick = React.useCallback(() => {
    dispatch(setNextCards());
  });

  return (
    <div className="flex justify-center">
      <div className="flex flex-col">
        <div>
          <div>{player.name}</div>
          <div>Points: {player.points}</div>
          <div>{player.current_card}</div>
        </div>
        <div>
          <div>{computer.current_card}</div>
          <div>Points: {computer.points}</div>
          <div>{computer.name}</div>
        </div>
        {!player.cards.length || !computer.cards.length ? (
          <Button
            onClick={handleReset}
            type="submit"
            color="primary"
            variant="contained"
          >
            Again?
          </Button>
        ) : (
          <Button
            onClick={handleClick}
            type="submit"
            color="primary"
            variant="contained"
          >
            next
          </Button>
        )}
        <div>
          {!player.cards.length || !computer.cards.length ? (
            player.points === computer.points ? (
              <h2>It's a tie</h2>
            ) : player.points > computer.points ? (
              <h2>{player.name} won!</h2>
            ) : (
              <h2>{computer.name} won!</h2>
            )
          ) : (
            ""
          )}
        </div>
      </div>
    </div>
  );
}

export default Game;