Typescript - Ensure Generic Property Exists On Generic Type With Descriptive Error
(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
add a comment |
(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
add a comment |
(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
(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
typescript generics
asked Jan 2 at 22:24
aaronofleonardaaronofleonard
1,9181017
1,9181017
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
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;
}
Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for thein
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
add a comment |
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');
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
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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;
}
Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for thein
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
add a comment |
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;
}
Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for thein
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
add a comment |
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;
}
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;
}
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 thein
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
add a comment |
Thanks, I knew there must have been a simple way of doing it. I was trying to find resources for thein
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
add a comment |
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');
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
add a comment |
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');
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
add a comment |
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');
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');
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
add a comment |
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
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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