John Winston
John Winston

Reputation: 1481

Switch inside a function doesn't work correctly in Typescript

I am trying to use Typescript with Redux, and design them according to Redux Ducks. I am struggling with this typecheck here and I cannot figure out why.


const UPDATE = "ticTacToe/board/update";
const RESET = "ticTacToe/board/reset";

export default function reducer(
  state = initialState,
  action: IBoardActions
): IBoardState {
  switch (action.type) {
    case UPDATE:
      const { nextPlayer, clickedIndex } = action;
            // TypeScript error: Property 'nextPlayer' does not exist on type 
           //'{ type: string; nextPlayer: string; clickedIndex: number; } | { type: string; }'.  TS2339
      const newSquares = [...state.squares];
      newSquares[clickedIndex] = nextPlayer;
      return {
        squares: newSquares,
        oIsNext: !state.oIsNext
      };
    case RESET:
      return initialState
    default:
      return state;
  }
}

type BoardAction = typeof updateBoard | typeof resetBoard;

export type IBoardActions = ReturnType<BoardAction>;

export function updateBoard(nextPlayer: string, clickedIndex: number) {
  return {
    type: UPDATE,
    nextPlayer,
    clickedIndex
  };
}

export function resetBoard() {
  return {
    type: RESET
  };
}

I have tried to fix it with enum, but this doesn't work as well. Is that the problem of switch?

enum Action {
  UPDATE = 'update',
  RESET = 'reset'
}

export default function reducer(
  state = initialState,
  action: IBoardActions
): IBoardState {
  switch (action.type) {
    case Action.UPDATE:
      const { nextPlayer, clickedIndex } = action;
      //TypeScript error: Property 'nextPlayer' does not exist on type '{ type: Ac
tion; nextPlayer: TicTacToeSymbols; clickedIndex: number; } | { type: Acti
on; }'.  TS2339
      const newSquares = [...state.squares];
      newSquares[clickedIndex] = nextPlayer;
      return {
        squares: newSquares,
        oIsNext: !state.oIsNext
      };
    case Action.RESET:
      return initialState
    default:
      return state;
  }
}

type BoardAction = typeof updateBoard | typeof resetBoard;

export type IBoardActions = ReturnType<BoardAction>;

export function updateBoard(nextPlayer: TicTacToeSymbols, clickedIndex: number) {
  return {
    type: Action.UPDATE,
    nextPlayer,
    clickedIndex
  };
}

export function resetBoard() {
  return {
    type: Action.RESET
  };
}

Why am I getting this error? It seems like my typing is correct based on the error message.

Upvotes: 0

Views: 73

Answers (3)

phry
phry

Reputation: 44326

We don't really encourage the use of discriminated unions any more as they have their own set of problems and require writing a lot of extra code.

The official recommendation is to use the official redux toolkit which has TypeScript support in mind and will reduce your code drastically. Please see the official redux tutorial on modern redux for a short introduction and then take a look at the essentials tutorial for more in-depth information.

Upvotes: 0

Vo Quoc Thang
Vo Quoc Thang

Reputation: 1396

You can also use interface like this.

export interface IBoardActions extends ReturnType<typeof updateBoard>, ReturnType<typeof resetBoard>

Upvotes: 0

Chase
Chase

Reputation: 5625

You're looking for discriminated unions. Note that the type of IBoardActions in your snippet is-

{ type: string; nextPlayer: string; clickedIndex: number; } | { type: string; }

But in reality, you want-

{ type: typeof UPDATE; nextPlayer: string; clickedIndex: number; } | { type: typeof RESET; }

This speaks explicitly that the first alternative is only used when the type property has the same type as that of UPDATE, and similarly for RESET.

But in your case, both UPDATE and RESET are of type string. There's no way to discriminate them.

This is where string literal types come in, change your definition of UPDATE and RESET to-

const UPDATE = "ticTacToe/board/update" as const;
const RESET = "ticTacToe/board/reset" as const;

The as const syntax is known as const assertions. If you notice, the type of UPDATE has now become a string literal set to its value, same for RESET.

Here's a playground demo

Upvotes: 1

Related Questions