Skip to content

Pick<Type, GenericParameter> throws new errors for attempting to access properties #28719

Closed

Description

TypeScript Version: 3.2.0-rc, 9319ea4

Search Terms:
Pick, keyof

Code

interface Foo {
    a: number;
    b: boolean;
}

const SomeFunc = <K extends keyof Foo>(
    state: Pick<Foo, K>
) => {
    if (state.a) {
        state.b = true;
    }
};

Expected behavior:
No error (passes in 3.1.6)

Actual behavior:

src/example.ts:17:15 - error TS2339: Property 'a' does not exist on type 'Pick<Foo, K>'.

17     if (state.a) {
                 ~

src/example.ts:19:15 - error TS2339: Property 'b' does not exist on type 'Pick<Foo, K>'.

19         state.b = true;

Playground Link:

Related Issues:
Possibly related to #28647, but I'm unsure.

Activity

  1. jack-williams commented on Nov 28, 2018

    @jack-williams
    Collaborator

    This implementation seems unsound given the follow instantiation.

    interface NotFoo {
        a: number,
        b: string;
    }
    
    const notFoo: NotFoo = { a: 42, b: "not-boolean" };
    SomeFunc<'a'>(notFoo);
  2. MLoughry commented on Nov 28, 2018

    @MLoughry
    Author

    That's plausible. Currently, the function in question in our code base is trying to pass the parameter on to React's setState, whose types have the following comment:

            // We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
            // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
            // Also, the ` | S` allows intellisense to not be dumbisense
            setState<K extends keyof S>(
                state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
                callback?: () => void
            ): void;

    In our actual usage, we're seeing if a property exists on the parameter; if so, we add more properties before calling setState:

        private setStateBasedOnMode = <K extends keyof DateSelectorState>(
            state: Pick<DateSelectorState, K>
        ) => {
            if (state.currentSelectorMode) {
                // update the shownViewStates if the currentSelectorMode is changed
                let shownViewStates = this.getShownViewState(state.currentSelectorMode);
                state.showEndTime = shownViewStates.showEndTime;
                state.showDuration = shownViewStates.showDuration;
                state.showEndDate = shownViewStates.showEndDate;
                state.showTimeOrDuration = shownViewStates.showTimeOrDuration;
                state.showTimeZones = shownViewStates.showTimeZones;
            }
    
            this.setState(state);
        };

    Is this something that would simply need to be fixed in @types/react? And/or is there a better method on our side?

  3. weswigham commented on Nov 29, 2018

    @weswigham
    Member

    Anders Hejlsberg (@ahejlsberg) we saw that strange pattern when we reviewed the rwc baselines for your change, if I recall. Did we decide what the better way to write that in a typesafe way was? Off the cuff I'd say a spread'd be better, personally.

  4. ahejlsberg commented on Nov 29, 2018

    @ahejlsberg
    Member

    Off the cuff I'd say a spread'd be better, personally.

    Agreed. A particularly troublesome aspect of the code example above is that it mutates the state input object. That could lead to some very subtle issues if a caller were to use a shared object instead of an object literal. With a spread you're guaranteed that the combined object is freshly allocated and not aliased.

  5. MLoughry commented on Nov 29, 2018

    @MLoughry
    Author

    Spread where, exactly? To get it to work, I still seem to have to type-cast it:

        private setStateBasedOnMode = <K extends keyof DateSelectorState>(
            state: Pick<DateSelectorState, K>
        ) => {
            if ((state as DateSelectorState).currentSelectorMode) {
                // update the shownViewStates if the currentSelectorMode is changed
                let shownViewStates = this.getShownViewState((state as DateSelectorState).currentSelectorMode);
                this.setState({
                    ...state,
                    showEndTime: shownViewStates.showEndTime,
                    showDuration: shownViewStates.showDuration,
                    showEndDate: shownViewStates.showEndDate,
                    showTimeOrDuration: shownViewStates.showTimeOrDuration,
                    showTimeZones: shownViewStates.showTimeZones,
                });
            } else {
                this.setState(state);
            }
        };
  6. weswigham commented on Nov 29, 2018

    @weswigham
    Member
        private setStateBasedOnMode = (
            state: Partial<DateSelectorState>
        ) => {
            if (state.currentSelectorMode) {
                // update the shownViewStates if the currentSelectorMode is changed
                let shownViewStates = this.getShownViewState(state.currentSelectorMode);
                this.setState({
                    ...state,
                    showEndTime: shownViewStates.showEndTime,
                    showDuration: shownViewStates.showDuration,
                    showEndDate: shownViewStates.showEndDate,
                    showTimeOrDuration: shownViewStates.showTimeOrDuration,
                    showTimeZones: shownViewStates.showTimeZones,
                });
            } else {
                this.setState(state);
            }
        };

    ?

  7. MLoughry commented on Nov 29, 2018

    @MLoughry
    Author

    Unfortunately, no. 😔

    packages/calendar/packages/calendar-common/owa-calendar-dateselector/lib/components/DateSelector.tsx:351:27 - error TS2345: Argument of type '{ showEndTime: boolean; showDuration: boolean; showEndDate: boolean; showTimeOrDuration: boolean; showTimeZones: boolean; currentSelectorMode?: DateSelectorMode; startDate?: OwaDate; endDate?: OwaDate; wasEndTimeSetManually?: boolean; shouldFocusEndTimePickerOnMount?: boolean; }' is not assignable to parameter of type 'DateSelectorState | ((prevState: Readonly<DateSelectorState>, props: DateSelectorProps) => DateSelectorState | Pick<DateSelectorState, "showTimeZones" | "currentSelectorMode" | ... 7 more ... | "shouldFocusEndTimePickerOnMount">) | Pick<...>'.
      Type '{ showEndTime: boolean; showDuration: boolean; showEndDate: boolean; showTimeOrDuration: boolean; showTimeZones: boolean; currentSelectorMode?: DateSelectorMode; startDate?: OwaDate; endDate?: OwaDate; wasEndTimeSetManually?: boolean; shouldFocusEndTimePickerOnMount?: boolean; }' is not assignable to type 'Pick<DateSelectorState, "showTimeZones" | "currentSelectorMode" | "showEndDate" | "showEndTime" | "showDuration" | "showTimeOrDuration" | "startDate" | "endDate" | "wasEndTimeSetManually" | "shouldFocusEndTimePickerOnMount">'.
        Property 'currentSelectorMode' is optional in type '{ showEndTime: boolean; showDuration: boolean; showEndDate: boolean; showTimeOrDuration: boolean; showTimeZones: boolean; currentSelectorMode?: DateSelectorMode; startDate?: OwaDate; endDate?: OwaDate; wasEndTimeSetManually?: boolean; shouldFocusEndTimePickerOnMount?: boolean; }' but required in type 'Pick<DateSelectorState, "showTimeZones" | "currentSelectorMode" | "showEndDate" | "showEndTime" | "showDuration" | "showTimeOrDuration" | "startDate" | "endDate" | "wasEndTimeSetManually" | "shouldFocusEndTimePickerOnMount">'.
    
    351             this.setState({
                                  ~
    352                 ...state,
        ~~~~~~~~~~~~~~~~~~~~~~~~~
    ...
    357                 showTimeZones: shownViewStates.showTimeZones,
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    358             });
        ~~~~~~~~~~~~~
    
    packages/calendar/packages/calendar-common/owa-calendar-dateselector/lib/components/DateSelector.tsx:360:27 - error TS2345: Argument of type 'Partial<DateSelectorState>' is not assignable to parameter of type 'DateSelectorState | ((prevState: Readonly<DateSelectorState>, props: DateSelectorProps) => DateSelectorState | Pick<DateSelectorState, "showTimeZones" | "currentSelectorMode"
    | ... 7 more ... | "shouldFocusEndTimePickerOnMount">) | Pick<...>'.
      Type 'Partial<DateSelectorState>' is not assignable to type 'Pick<DateSelectorState, "showTimeZones" | "currentSelectorMode" | "showEndDate" | "showEndTime" | "showDuration" | "showTimeOrDuration" | "startDate" | "endDate" | "wasEndTimeSetManually" | "shouldFocusEndTimePickerOnMount">'.
        Property 'showTimeZones' is optional in type 'Partial<DateSelectorState>' but required in type 'Pick<DateSelectorState, "showTimeZones" | "currentSelectorMode" | "showEndDate" | "showEndTime" | "showDuration" | "showTimeOrDuration" | "startDate" | "endDate" | "wasEndTimeSetManually" | "shouldFocusEndTimePickerOnMount">'.
    
    360             this.setState(state);
                                  ~~~~~
    
  8. TLadd commented on Jan 9, 2019

    @TLadd

    Also ran into this today when upgrading typescript. Similar use-case where I'm accepting an argument that gets passed to React's setState with potentially additional data added depending on which fields are passed in.

  9. typescript-bot commented on Jul 13, 2019

    @typescript-bot
    Contributor

    This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.

  10. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Domain: JSX/TSXRelates to the JSX parser and emitterQuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions