that_guy
that_guy

Reputation: 2445

ngrx reducer Property does not exist on type

I'm using TypeScript 2.7.1 and ngrx. My Actions look like this:

import { Action } from '@ngrx/store';


export const HEALTH_FORM_CONTENT_CHANGED = '[setupHealthForm] Content Changed';
export const HEALTH_FORM_TITLE_CHANGED = '[setupHealthForm] Title Changed';


export class SetupHealthFormContentChangedAction implements Action {
    public type: string = HEALTH_FORM_CONTENT_CHANGED;
    constructor(public payload: { content: string }) { }
}

export class SetupHealthFormTitleChangedAction implements Action {
    public type: string = HEALTH_FORM_TITLE_CHANGED;
    constructor(public payload: { title: string }) { }
}
export type Actions
    =
    SetupHealthFormContentChangedAction
    | SetupHealthFormTitleChangedAction;

my Reducer looks like this:

import { Actions, HEALTH_FORM_TITLE_CHANGED, HEALTH_FORM_CONTENT_CHANGED } from './setup-health-form.actions';


export interface State {
    title: string;
    body: string;
}

const initialState: State = {
    title: '',
    body: ''
}

export function reducer(state: State = initialState, action: Actions): State {

    switch (action.type) {
        case HEALTH_FORM_TITLE_CHANGED: { 
            return {
                ...state,
                ...{ title: action.payload.title }
            }
        }
        case HEALTH_FORM_CONTENT_CHANGED: {
            return {
                ...state,
                ...{ body: action.payload.content }
            }
        }
        default: {
            return state;
        }
    }
}

export const body = (state: State) => state.body;
export const title = (state: State) => state.title;

However I get the following typescript errors:

error TS2339: Property 'title' does not exist on type '{ content: string; } | { title: string; }'.
error TS2339: Property 'content' does not exist on type '{ content: string; } | { title: string; }'.

The only way i've found to fix this is exporting an action with a payload of type any. How do I correctly fix this issue to retain my typings?

Upvotes: 3

Views: 2352

Answers (2)

ThangLeQuoc
ThangLeQuoc

Reputation: 3073

Added to the existing answer of Titian, for my case, the error occurs if the type definition is a string is concatenated (this is the behavior of my IDE setting called hard wrap to auto break if the line is too long). You will need to make sure the type is a concrete one single const string.

Not working

export const GET_SYSTEM_CLOSING_REPORT_OF_PROJECT_MANAGER = '[MonthClosingReportActions] Get System Closing Reports of' +
    ' project manager';
export class GetSystemClosingReportOfProjectManagerAction implements Action {
    readonly type = GET_SYSTEM_CLOSING_REPORT_OF_PROJECT_MANAGER;
    constructor(public payload: string) {}
}

Working

export const GET_SYSTEM_CLOSING_REPORT_OF_PROJECT_MANAGER = '[MonthClosingReportActions] Get System Closing Reports of project manager';
export class GetSystemClosingReportOfProjectManagerAction implements Action {
    readonly type = GET_SYSTEM_CLOSING_REPORT_OF_PROJECT_MANAGER;
    constructor(public payload: string) {}
}

Upvotes: 0

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249466

To use discriminated union types and their switch type guard behavior, the type of type has to be a string literal type (basically a string type that can only be a single value). Your type field is string even through you assign a constant to it. This happens because typescript assumes that you will want ot mutate the field so it types it as string. If you mark it as readonly and remove the explicit string type the field will be typed with the type of the constant (which is a string literal type) and your switch will correctly type check:

export class SetupHealthFormContentChangedAction  {
    public readonly type = HEALTH_FORM_CONTENT_CHANGED;
    constructor(public payload: { content: string }) { }
}

export class SetupHealthFormTitleChangedAction implements Action {
    public  readonly type = HEALTH_FORM_TITLE_CHANGED
    constructor(public payload: { title: string }) { }

Playground sample code

Upvotes: 5

Related Questions