Kyanite
Kyanite

Reputation: 746

Property does not exist on type within Redux reducer using TypeScript?

I'm trying to use Redux with TypeScript in a little learning project, following this tutorial: https://redux.js.org/recipes/usage-with-typescript/

const INITIAL_STATE: TweetsState = {
  tweets: []
};

const tweetsReducer: Reducer<TweetsState> = (
  state = INITIAL_STATE,
  action: TweetAction
): TweetsState => {
  switch (action.type) {
    case GET_TWEETS:
      return {
        ...state
      };

    case TWEETS_RECEIVED:
      return {
        tweets: [...state.tweets, action.payload]
      };

    default:
      return state;
  }
};

My reducer expects an action of type TweetAction, which is the union of two interfaces:

export interface IGetTweetsAction {
  type: typeof GET_TWEETS;
}

export interface ITweetsReceivedAction {
  type: typeof TWEETS_RECEIVED;
  payload: ITweet[];
}

export const GET_TWEETS: string = "GET_TWEETS";
export const TWEETS_RECEIVED: string = "TWEETS_RECEIVED";

export type TweetAction = IGetTweetsAction | ITweetsReceivedAction;

However, within my reducer, I get the error Property 'payload' does not exist on type 'TweetAction' which is confusing because it suggests the union hasn't worked?

But this is further confusing because within my actions file it clearly allows me to return an object, of type TweetAction, with a payload:

import {
  TweetAction,
  GET_TWEETS,
  TWEETS_RECEIVED,
  ITweet
} from "./tweets.types";

export const getTweets = (): TweetAction => ({
  type: GET_TWEETS
});

export const tweetsReceived = (tweets: ITweet[]): TweetAction => ({
  type: TWEETS_RECEIVED,
  payload: tweets
});

I'm aware the code is currently a bit of a mess (I sometimes instinctively prefix interfaces with I being a C# dev, sorry!) as I haven't been through a refactory stage yet as I'm just trying to whip something up quickly, but I've been stumped by this error.

Can anyone see what's going wrong? It's probably something straight forward but the isolation might be getting to my head.

Thanks!

Edit: Note that removing the typesafety and changing: action: TweetAction to just action allows me to do action.payload and the functionality works fine...

Upvotes: 2

Views: 4957

Answers (1)

Nicolas SEPTIER
Nicolas SEPTIER

Reputation: 851

-- edit -----

Ok So apparently you need to remove the : string typing in those lines:

export const GET_TWEETS: string = "GET_TWEETS";
export const TWEETS_RECEIVED: string = "TWEETS_RECEIVED";

-- /edit -----

I'm pretty new to typescript too, but as I understand it, since you use action.payload in your reducer, the type TweetAction is not accepted since it could imply a IGetTweetsAction type, which does not define a payload property.

You need to use a more generic type like: type TweetAction { type: // ..., }

to say that you can have an action that will always have a type and might have some other props (such as a payload).

As for your confusing example, I think that actually makes sense:

export const getTweets = (): TweetAction => ({
  type: GET_TWEETS
});

No payload is defined so it matches IGetTweetsAction.

export const tweetsReceived = (tweets: ITweet[]): TweetAction => ({
  type: TWEETS_RECEIVED,
  payload: tweets
});

While this one matches ITweetsReceivedAction.

Upvotes: 1

Related Questions