Typescript - Ensure Generic Property Exists On Generic Type With Descriptive Error












2















(Typescript newbie warning)



I am creating a reusable reducer which takes a state and an action and returns the state, but is limited to accepting state which contains a certain type at a given key. This key is passed as a parameter of a function. If the state object that is passed does not contain the key that is passed, the compiler should raise an error.



Now, I got this working. However the error message generated by the compiler does not adequately, in my estimation, describe the problem. It doesn't say that "x property is missing on type", instead it gives other errors, which I will detail below.



Types



// FetchAction up here, not so relevant...

export type FetchState = {
status: FetchAction,
timestamp: Date
} | null

export type LoginState = {
token: string | null,
fetching: FetchState
};


Base Reducer



const intialState: LoginState = {
token: null,
fetching: null
}

const loginReducer: Reducer<LoginState> = (state = intialState, action) => {
//...other operations
return fetchingReducer(state, action, 'fetching');
}


Method 1



type FetchContainingState<S, K extends keyof S> = {
[F in keyof S]: F extends K ? FetchState : S[F];
};

export const fetchingReducer = <S extends FetchContainingState<S, K>, K extends keyof S>(state: S, action: Action, key: K): S => {
// implementation
}


This works properly. If I change the function call to:
return fetchingReducer(state, action, 'fetchin'); (misspelling fetching), then I get this error:




Argument of type 'LoginState' is not assignable to parameter of type
'FetchContainingState'. Types of
property 'token' are incompatible.
Type 'string | null' is not assignable to type 'FetchState'.
Type 'string' is not assignable to type 'FetchState'.




Well, it's good that it gives me an error. However, it just warns me about "token", or whatever other property exists on the object. It doesn't give me any direct indication as to which property it expects but doesn't exist.



Method 2



type EnsureFetchState<S, K extends keyof S> = S[K] extends FetchState ? S : never;

export const fetchingReducer = <S, K extends keyof S>(state: EnsureFetchState<S, K>, action: Action, key: K): S => {
// implementation
}


This works as well, when I change the call to return fetchingReducer(state, action, 'fetchin'); (misspelling "fetching"), I get:




Argument of type 'LoginState' is not assignable to parameter of type 'never'.




More succinct, but even less descriptive of an error. It gives even less indication as to what could be wrong with the arguments that were passed.



Conclusion



In Method 1 I used a Mapped Type, and in Method 2 I used a Conditional Type to determine that the values for state and key that I passed did not pass the criteria we were searching for. However, in both cases the error messages do not really describe what the real problem is.



I'm new with more advanced types in Typescript, so there might be a really simple way of doing this or a simple concept that I've overlooked. I hope so! But anyways, the gist of this is: How can this type check on an object with a dynamic key be done more idiomatically, or in a way where the compiler generates a more beneficial error message?










share|improve this question



























    2















    (Typescript newbie warning)



    I am creating a reusable reducer which takes a state and an action and returns the state, but is limited to accepting state which contains a certain type at a given key. This key is passed as a parameter of a function. If the state object that is passed does not contain the key that is passed, the compiler should raise an error.



    Now, I got this working. However the error message generated by the compiler does not adequately, in my estimation, describe the problem. It doesn't say that "x property is missing on type", instead it gives other errors, which I will detail below.



    Types



    // FetchAction up here, not so relevant...

    export type FetchState = {
    status: FetchAction,
    timestamp: Date
    } | null

    export type LoginState = {
    token: string | null,
    fetching: FetchState
    };


    Base Reducer



    const intialState: LoginState = {
    token: null,
    fetching: null
    }

    const loginReducer: Reducer<LoginState> = (state = intialState, action) => {
    //...other operations
    return fetchingReducer(state, action, 'fetching');
    }


    Method 1



    type FetchContainingState<S, K extends keyof S> = {
    [F in keyof S]: F extends K ? FetchState : S[F];
    };

    export const fetchingReducer = <S extends FetchContainingState<S, K>, K extends keyof S>(state: S, action: Action, key: K): S => {
    // implementation
    }


    This works properly. If I change the function call to:
    return fetchingReducer(state, action, 'fetchin'); (misspelling fetching), then I get this error:




    Argument of type 'LoginState' is not assignable to parameter of type
    'FetchContainingState'. Types of
    property 'token' are incompatible.
    Type 'string | null' is not assignable to type 'FetchState'.
    Type 'string' is not assignable to type 'FetchState'.




    Well, it's good that it gives me an error. However, it just warns me about "token", or whatever other property exists on the object. It doesn't give me any direct indication as to which property it expects but doesn't exist.



    Method 2



    type EnsureFetchState<S, K extends keyof S> = S[K] extends FetchState ? S : never;

    export const fetchingReducer = <S, K extends keyof S>(state: EnsureFetchState<S, K>, action: Action, key: K): S => {
    // implementation
    }


    This works as well, when I change the call to return fetchingReducer(state, action, 'fetchin'); (misspelling "fetching"), I get:




    Argument of type 'LoginState' is not assignable to parameter of type 'never'.




    More succinct, but even less descriptive of an error. It gives even less indication as to what could be wrong with the arguments that were passed.



    Conclusion



    In Method 1 I used a Mapped Type, and in Method 2 I used a Conditional Type to determine that the values for state and key that I passed did not pass the criteria we were searching for. However, in both cases the error messages do not really describe what the real problem is.



    I'm new with more advanced types in Typescript, so there might be a really simple way of doing this or a simple concept that I've overlooked. I hope so! But anyways, the gist of this is: How can this type check on an object with a dynamic key be done more idiomatically, or in a way where the compiler generates a more beneficial error message?










    share|improve this question

























      2












      2








      2








      (Typescript newbie warning)



      I am creating a reusable reducer which takes a state and an action and returns the state, but is limited to accepting state which contains a certain type at a given key. This key is passed as a parameter of a function. If the state object that is passed does not contain the key that is passed, the compiler should raise an error.



      Now, I got this working. However the error message generated by the compiler does not adequately, in my estimation, describe the problem. It doesn't say that "x property is missing on type", instead it gives other errors, which I will detail below.



      Types



      // FetchAction up here, not so relevant...

      export type FetchState = {
      status: FetchAction,
      timestamp: Date
      } | null

      export type LoginState = {
      token: string | null,
      fetching: FetchState
      };


      Base Reducer



      const intialState: LoginState = {
      token: null,
      fetching: null
      }

      const loginReducer: Reducer<LoginState> = (state = intialState, action) => {
      //...other operations
      return fetchingReducer(state, action, 'fetching');
      }


      Method 1



      type FetchContainingState<S, K extends keyof S> = {
      [F in keyof S]: F extends K ? FetchState : S[F];
      };

      export const fetchingReducer = <S extends FetchContainingState<S, K>, K extends keyof S>(state: S, action: Action, key: K): S => {
      // implementation
      }


      This works properly. If I change the function call to:
      return fetchingReducer(state, action, 'fetchin'); (misspelling fetching), then I get this error:




      Argument of type 'LoginState' is not assignable to parameter of type
      'FetchContainingState'. Types of
      property 'token' are incompatible.
      Type 'string | null' is not assignable to type 'FetchState'.
      Type 'string' is not assignable to type 'FetchState'.




      Well, it's good that it gives me an error. However, it just warns me about "token", or whatever other property exists on the object. It doesn't give me any direct indication as to which property it expects but doesn't exist.



      Method 2



      type EnsureFetchState<S, K extends keyof S> = S[K] extends FetchState ? S : never;

      export const fetchingReducer = <S, K extends keyof S>(state: EnsureFetchState<S, K>, action: Action, key: K): S => {
      // implementation
      }


      This works as well, when I change the call to return fetchingReducer(state, action, 'fetchin'); (misspelling "fetching"), I get:




      Argument of type 'LoginState' is not assignable to parameter of type 'never'.




      More succinct, but even less descriptive of an error. It gives even less indication as to what could be wrong with the arguments that were passed.



      Conclusion



      In Method 1 I used a Mapped Type, and in Method 2 I used a Conditional Type to determine that the values for state and key that I passed did not pass the criteria we were searching for. However, in both cases the error messages do not really describe what the real problem is.



      I'm new with more advanced types in Typescript, so there might be a really simple way of doing this or a simple concept that I've overlooked. I hope so! But anyways, the gist of this is: How can this type check on an object with a dynamic key be done more idiomatically, or in a way where the compiler generates a more beneficial error message?










      share|improve this question














      (Typescript newbie warning)



      I am creating a reusable reducer which takes a state and an action and returns the state, but is limited to accepting state which contains a certain type at a given key. This key is passed as a parameter of a function. If the state object that is passed does not contain the key that is passed, the compiler should raise an error.



      Now, I got this working. However the error message generated by the compiler does not adequately, in my estimation, describe the problem. It doesn't say that "x property is missing on type", instead it gives other errors, which I will detail below.



      Types



      // FetchAction up here, not so relevant...

      export type FetchState = {
      status: FetchAction,
      timestamp: Date
      } | null

      export type LoginState = {
      token: string | null,
      fetching: FetchState
      };


      Base Reducer



      const intialState: LoginState = {
      token: null,
      fetching: null
      }

      const loginReducer: Reducer<LoginState> = (state = intialState, action) => {
      //...other operations
      return fetchingReducer(state, action, 'fetching');
      }


      Method 1



      type FetchContainingState<S, K extends keyof S> = {
      [F in keyof S]: F extends K ? FetchState : S[F];
      };

      export const fetchingReducer = <S extends FetchContainingState<S, K>, K extends keyof S>(state: S, action: Action, key: K): S => {
      // implementation
      }


      This works properly. If I change the function call to:
      return fetchingReducer(state, action, 'fetchin'); (misspelling fetching), then I get this error:




      Argument of type 'LoginState' is not assignable to parameter of type
      'FetchContainingState'. Types of
      property 'token' are incompatible.
      Type 'string | null' is not assignable to type 'FetchState'.
      Type 'string' is not assignable to type 'FetchState'.




      Well, it's good that it gives me an error. However, it just warns me about "token", or whatever other property exists on the object. It doesn't give me any direct indication as to which property it expects but doesn't exist.



      Method 2



      type EnsureFetchState<S, K extends keyof S> = S[K] extends FetchState ? S : never;

      export const fetchingReducer = <S, K extends keyof S>(state: EnsureFetchState<S, K>, action: Action, key: K): S => {
      // implementation
      }


      This works as well, when I change the call to return fetchingReducer(state, action, 'fetchin'); (misspelling "fetching"), I get:




      Argument of type 'LoginState' is not assignable to parameter of type 'never'.




      More succinct, but even less descriptive of an error. It gives even less indication as to what could be wrong with the arguments that were passed.



      Conclusion



      In Method 1 I used a Mapped Type, and in Method 2 I used a Conditional Type to determine that the values for state and key that I passed did not pass the criteria we were searching for. However, in both cases the error messages do not really describe what the real problem is.



      I'm new with more advanced types in Typescript, so there might be a really simple way of doing this or a simple concept that I've overlooked. I hope so! But anyways, the gist of this is: How can this type check on an object with a dynamic key be done more idiomatically, or in a way where the compiler generates a more beneficial error message?







      typescript generics






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Jan 2 at 22:24









      aaronofleonardaaronofleonard

      1,9181017




      1,9181017
























          2 Answers
          2






          active

          oldest

          votes


















          2














          When describing type constraints, it usually helps to be as direct as possible. Constraint for a type S which has a property named K with type FetchState does not need to mention other properties:



          export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {


          This seems to produce desired error messages, at least with this example code (I just made up definitions for missing types to make it complete):



          export interface Action {
          a: string;
          }
          export interface FetchAction extends Action {
          f: string;
          }
          export type FetchState = {
          status: FetchAction,
          timestamp: Date
          } | null
          export type LoginState = {
          token: string | null,
          fetching: FetchState
          };
          const intialState: LoginState = {
          token: null,
          fetching: null
          }
          export type Reducer<S> = (s: S, a: Action) => S;

          const loginReducer: Reducer<LoginState> = (state = intialState, action) => {

          fetchingReducer(state, action, 'fetcing'); // Argument of type 'LoginState'
          // is not assignable to parameter of type '{ fetcing: FetchState; }'.
          // Property 'fetcing' is missing in type 'LoginState'.


          fetchingReducer(state, action, 'token'); // Argument of type 'LoginState' is
          // not assignable to parameter of type '{ token: FetchState; }'.
          // Types of property 'token' are incompatible.
          // Type 'string | null' is not assignable to type 'FetchState'.
          // Type 'string' is not assignable to type 'FetchState'.


          // OK
          return fetchingReducer(state, action, 'fetching')
          }

          export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {
          return {} as S;
          }





          share|improve this answer
























          • Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for the in keyword in Typescript...but such a small word is kind of hard to google for heh, it appears on every page! I didn't know it could be used like this, so I'm happy to see that it can be. This is pretty much what I was looking for ideally, thank you very much.

            – aaronofleonard
            Jan 3 at 15:51



















          2














          The readability of the errors, much like beauty is in the eye of the beholder.



          In my opinion, the prettiest error I can get is by adding a second overload where K is PropertyKey. The error is triggered by a conditional type, but the conditional type is added on the key parameter. The need of the second overload comes from the fact that if K extends keyof S and there is an error on key K will get inferred to keyof S instead of the actual value.



          For the error part, I use a string literal type with a descriptive message. If you have a key named "This porperty is not of type FetchState" you might have an issue with this, but this seems unlikely.



          type EnsureFetchState<S, K extends PropertyKey> = S extends Record<K, FetchState> ? {} : "This porperty is not of type FetchState";
          export function fetchingReducer<S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
          export function fetchingReducer <S, K extends PropertyKey>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
          export function fetchingReducer <S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S {
          // implementation
          }

          //Argument of type '"fetchin"' is not assignable to parameter of type '"fetchin" & "This porperty is not of type FetchState"'.
          return fetchingReducer(state, action, 'fetchin');





          share|improve this answer
























          • Very nice, I really enjoy the ingenuity of this solution. The other answer did give me a result closer to what I was looking for, but I'm still adding this technique to my goodie bag. Thank you very much!

            – aaronofleonard
            Jan 3 at 15:49











          • @aaronofleonard you are welcomed arthem's answer is also good :)

            – Titian Cernicova-Dragomir
            Jan 3 at 15:51












          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54013952%2ftypescript-ensure-generic-property-exists-on-generic-type-with-descriptive-err%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          2 Answers
          2






          active

          oldest

          votes








          2 Answers
          2






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          2














          When describing type constraints, it usually helps to be as direct as possible. Constraint for a type S which has a property named K with type FetchState does not need to mention other properties:



          export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {


          This seems to produce desired error messages, at least with this example code (I just made up definitions for missing types to make it complete):



          export interface Action {
          a: string;
          }
          export interface FetchAction extends Action {
          f: string;
          }
          export type FetchState = {
          status: FetchAction,
          timestamp: Date
          } | null
          export type LoginState = {
          token: string | null,
          fetching: FetchState
          };
          const intialState: LoginState = {
          token: null,
          fetching: null
          }
          export type Reducer<S> = (s: S, a: Action) => S;

          const loginReducer: Reducer<LoginState> = (state = intialState, action) => {

          fetchingReducer(state, action, 'fetcing'); // Argument of type 'LoginState'
          // is not assignable to parameter of type '{ fetcing: FetchState; }'.
          // Property 'fetcing' is missing in type 'LoginState'.


          fetchingReducer(state, action, 'token'); // Argument of type 'LoginState' is
          // not assignable to parameter of type '{ token: FetchState; }'.
          // Types of property 'token' are incompatible.
          // Type 'string | null' is not assignable to type 'FetchState'.
          // Type 'string' is not assignable to type 'FetchState'.


          // OK
          return fetchingReducer(state, action, 'fetching')
          }

          export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {
          return {} as S;
          }





          share|improve this answer
























          • Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for the in keyword in Typescript...but such a small word is kind of hard to google for heh, it appears on every page! I didn't know it could be used like this, so I'm happy to see that it can be. This is pretty much what I was looking for ideally, thank you very much.

            – aaronofleonard
            Jan 3 at 15:51
















          2














          When describing type constraints, it usually helps to be as direct as possible. Constraint for a type S which has a property named K with type FetchState does not need to mention other properties:



          export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {


          This seems to produce desired error messages, at least with this example code (I just made up definitions for missing types to make it complete):



          export interface Action {
          a: string;
          }
          export interface FetchAction extends Action {
          f: string;
          }
          export type FetchState = {
          status: FetchAction,
          timestamp: Date
          } | null
          export type LoginState = {
          token: string | null,
          fetching: FetchState
          };
          const intialState: LoginState = {
          token: null,
          fetching: null
          }
          export type Reducer<S> = (s: S, a: Action) => S;

          const loginReducer: Reducer<LoginState> = (state = intialState, action) => {

          fetchingReducer(state, action, 'fetcing'); // Argument of type 'LoginState'
          // is not assignable to parameter of type '{ fetcing: FetchState; }'.
          // Property 'fetcing' is missing in type 'LoginState'.


          fetchingReducer(state, action, 'token'); // Argument of type 'LoginState' is
          // not assignable to parameter of type '{ token: FetchState; }'.
          // Types of property 'token' are incompatible.
          // Type 'string | null' is not assignable to type 'FetchState'.
          // Type 'string' is not assignable to type 'FetchState'.


          // OK
          return fetchingReducer(state, action, 'fetching')
          }

          export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {
          return {} as S;
          }





          share|improve this answer
























          • Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for the in keyword in Typescript...but such a small word is kind of hard to google for heh, it appears on every page! I didn't know it could be used like this, so I'm happy to see that it can be. This is pretty much what I was looking for ideally, thank you very much.

            – aaronofleonard
            Jan 3 at 15:51














          2












          2








          2







          When describing type constraints, it usually helps to be as direct as possible. Constraint for a type S which has a property named K with type FetchState does not need to mention other properties:



          export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {


          This seems to produce desired error messages, at least with this example code (I just made up definitions for missing types to make it complete):



          export interface Action {
          a: string;
          }
          export interface FetchAction extends Action {
          f: string;
          }
          export type FetchState = {
          status: FetchAction,
          timestamp: Date
          } | null
          export type LoginState = {
          token: string | null,
          fetching: FetchState
          };
          const intialState: LoginState = {
          token: null,
          fetching: null
          }
          export type Reducer<S> = (s: S, a: Action) => S;

          const loginReducer: Reducer<LoginState> = (state = intialState, action) => {

          fetchingReducer(state, action, 'fetcing'); // Argument of type 'LoginState'
          // is not assignable to parameter of type '{ fetcing: FetchState; }'.
          // Property 'fetcing' is missing in type 'LoginState'.


          fetchingReducer(state, action, 'token'); // Argument of type 'LoginState' is
          // not assignable to parameter of type '{ token: FetchState; }'.
          // Types of property 'token' are incompatible.
          // Type 'string | null' is not assignable to type 'FetchState'.
          // Type 'string' is not assignable to type 'FetchState'.


          // OK
          return fetchingReducer(state, action, 'fetching')
          }

          export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {
          return {} as S;
          }





          share|improve this answer













          When describing type constraints, it usually helps to be as direct as possible. Constraint for a type S which has a property named K with type FetchState does not need to mention other properties:



          export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {


          This seems to produce desired error messages, at least with this example code (I just made up definitions for missing types to make it complete):



          export interface Action {
          a: string;
          }
          export interface FetchAction extends Action {
          f: string;
          }
          export type FetchState = {
          status: FetchAction,
          timestamp: Date
          } | null
          export type LoginState = {
          token: string | null,
          fetching: FetchState
          };
          const intialState: LoginState = {
          token: null,
          fetching: null
          }
          export type Reducer<S> = (s: S, a: Action) => S;

          const loginReducer: Reducer<LoginState> = (state = intialState, action) => {

          fetchingReducer(state, action, 'fetcing'); // Argument of type 'LoginState'
          // is not assignable to parameter of type '{ fetcing: FetchState; }'.
          // Property 'fetcing' is missing in type 'LoginState'.


          fetchingReducer(state, action, 'token'); // Argument of type 'LoginState' is
          // not assignable to parameter of type '{ token: FetchState; }'.
          // Types of property 'token' are incompatible.
          // Type 'string | null' is not assignable to type 'FetchState'.
          // Type 'string' is not assignable to type 'FetchState'.


          // OK
          return fetchingReducer(state, action, 'fetching')
          }

          export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {
          return {} as S;
          }






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Jan 2 at 23:09









          artemartem

          15.6k12940




          15.6k12940













          • Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for the in keyword in Typescript...but such a small word is kind of hard to google for heh, it appears on every page! I didn't know it could be used like this, so I'm happy to see that it can be. This is pretty much what I was looking for ideally, thank you very much.

            – aaronofleonard
            Jan 3 at 15:51



















          • Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for the in keyword in Typescript...but such a small word is kind of hard to google for heh, it appears on every page! I didn't know it could be used like this, so I'm happy to see that it can be. This is pretty much what I was looking for ideally, thank you very much.

            – aaronofleonard
            Jan 3 at 15:51

















          Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for the in keyword in Typescript...but such a small word is kind of hard to google for heh, it appears on every page! I didn't know it could be used like this, so I'm happy to see that it can be. This is pretty much what I was looking for ideally, thank you very much.

          – aaronofleonard
          Jan 3 at 15:51





          Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for the in keyword in Typescript...but such a small word is kind of hard to google for heh, it appears on every page! I didn't know it could be used like this, so I'm happy to see that it can be. This is pretty much what I was looking for ideally, thank you very much.

          – aaronofleonard
          Jan 3 at 15:51













          2














          The readability of the errors, much like beauty is in the eye of the beholder.



          In my opinion, the prettiest error I can get is by adding a second overload where K is PropertyKey. The error is triggered by a conditional type, but the conditional type is added on the key parameter. The need of the second overload comes from the fact that if K extends keyof S and there is an error on key K will get inferred to keyof S instead of the actual value.



          For the error part, I use a string literal type with a descriptive message. If you have a key named "This porperty is not of type FetchState" you might have an issue with this, but this seems unlikely.



          type EnsureFetchState<S, K extends PropertyKey> = S extends Record<K, FetchState> ? {} : "This porperty is not of type FetchState";
          export function fetchingReducer<S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
          export function fetchingReducer <S, K extends PropertyKey>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
          export function fetchingReducer <S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S {
          // implementation
          }

          //Argument of type '"fetchin"' is not assignable to parameter of type '"fetchin" & "This porperty is not of type FetchState"'.
          return fetchingReducer(state, action, 'fetchin');





          share|improve this answer
























          • Very nice, I really enjoy the ingenuity of this solution. The other answer did give me a result closer to what I was looking for, but I'm still adding this technique to my goodie bag. Thank you very much!

            – aaronofleonard
            Jan 3 at 15:49











          • @aaronofleonard you are welcomed arthem's answer is also good :)

            – Titian Cernicova-Dragomir
            Jan 3 at 15:51
















          2














          The readability of the errors, much like beauty is in the eye of the beholder.



          In my opinion, the prettiest error I can get is by adding a second overload where K is PropertyKey. The error is triggered by a conditional type, but the conditional type is added on the key parameter. The need of the second overload comes from the fact that if K extends keyof S and there is an error on key K will get inferred to keyof S instead of the actual value.



          For the error part, I use a string literal type with a descriptive message. If you have a key named "This porperty is not of type FetchState" you might have an issue with this, but this seems unlikely.



          type EnsureFetchState<S, K extends PropertyKey> = S extends Record<K, FetchState> ? {} : "This porperty is not of type FetchState";
          export function fetchingReducer<S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
          export function fetchingReducer <S, K extends PropertyKey>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
          export function fetchingReducer <S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S {
          // implementation
          }

          //Argument of type '"fetchin"' is not assignable to parameter of type '"fetchin" & "This porperty is not of type FetchState"'.
          return fetchingReducer(state, action, 'fetchin');





          share|improve this answer
























          • Very nice, I really enjoy the ingenuity of this solution. The other answer did give me a result closer to what I was looking for, but I'm still adding this technique to my goodie bag. Thank you very much!

            – aaronofleonard
            Jan 3 at 15:49











          • @aaronofleonard you are welcomed arthem's answer is also good :)

            – Titian Cernicova-Dragomir
            Jan 3 at 15:51














          2












          2








          2







          The readability of the errors, much like beauty is in the eye of the beholder.



          In my opinion, the prettiest error I can get is by adding a second overload where K is PropertyKey. The error is triggered by a conditional type, but the conditional type is added on the key parameter. The need of the second overload comes from the fact that if K extends keyof S and there is an error on key K will get inferred to keyof S instead of the actual value.



          For the error part, I use a string literal type with a descriptive message. If you have a key named "This porperty is not of type FetchState" you might have an issue with this, but this seems unlikely.



          type EnsureFetchState<S, K extends PropertyKey> = S extends Record<K, FetchState> ? {} : "This porperty is not of type FetchState";
          export function fetchingReducer<S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
          export function fetchingReducer <S, K extends PropertyKey>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
          export function fetchingReducer <S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S {
          // implementation
          }

          //Argument of type '"fetchin"' is not assignable to parameter of type '"fetchin" & "This porperty is not of type FetchState"'.
          return fetchingReducer(state, action, 'fetchin');





          share|improve this answer













          The readability of the errors, much like beauty is in the eye of the beholder.



          In my opinion, the prettiest error I can get is by adding a second overload where K is PropertyKey. The error is triggered by a conditional type, but the conditional type is added on the key parameter. The need of the second overload comes from the fact that if K extends keyof S and there is an error on key K will get inferred to keyof S instead of the actual value.



          For the error part, I use a string literal type with a descriptive message. If you have a key named "This porperty is not of type FetchState" you might have an issue with this, but this seems unlikely.



          type EnsureFetchState<S, K extends PropertyKey> = S extends Record<K, FetchState> ? {} : "This porperty is not of type FetchState";
          export function fetchingReducer<S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
          export function fetchingReducer <S, K extends PropertyKey>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
          export function fetchingReducer <S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S {
          // implementation
          }

          //Argument of type '"fetchin"' is not assignable to parameter of type '"fetchin" & "This porperty is not of type FetchState"'.
          return fetchingReducer(state, action, 'fetchin');






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Jan 2 at 23:21









          Titian Cernicova-DragomirTitian Cernicova-Dragomir

          72.4k35068




          72.4k35068













          • Very nice, I really enjoy the ingenuity of this solution. The other answer did give me a result closer to what I was looking for, but I'm still adding this technique to my goodie bag. Thank you very much!

            – aaronofleonard
            Jan 3 at 15:49











          • @aaronofleonard you are welcomed arthem's answer is also good :)

            – Titian Cernicova-Dragomir
            Jan 3 at 15:51



















          • Very nice, I really enjoy the ingenuity of this solution. The other answer did give me a result closer to what I was looking for, but I'm still adding this technique to my goodie bag. Thank you very much!

            – aaronofleonard
            Jan 3 at 15:49











          • @aaronofleonard you are welcomed arthem's answer is also good :)

            – Titian Cernicova-Dragomir
            Jan 3 at 15:51

















          Very nice, I really enjoy the ingenuity of this solution. The other answer did give me a result closer to what I was looking for, but I'm still adding this technique to my goodie bag. Thank you very much!

          – aaronofleonard
          Jan 3 at 15:49





          Very nice, I really enjoy the ingenuity of this solution. The other answer did give me a result closer to what I was looking for, but I'm still adding this technique to my goodie bag. Thank you very much!

          – aaronofleonard
          Jan 3 at 15:49













          @aaronofleonard you are welcomed arthem's answer is also good :)

          – Titian Cernicova-Dragomir
          Jan 3 at 15:51





          @aaronofleonard you are welcomed arthem's answer is also good :)

          – Titian Cernicova-Dragomir
          Jan 3 at 15:51


















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54013952%2ftypescript-ensure-generic-property-exists-on-generic-type-with-descriptive-err%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          MongoDB - Not Authorized To Execute Command

          How to fix TextFormField cause rebuild widget in Flutter

          in spring boot 2.1 many test slices are not allowed anymore due to multiple @BootstrapWith