Cannot spread extra values to instance of extended interface
I'm trying to define a data injector (in context: props
option of Apollo Client's graphql
HOC) and I have found out that my current implementation incorrectly allows some mistakes to go uncaught by TypeScript.
I have tried many variants as exposed below but I can't find the correct way to propagate extra input along with the injected data and I'm still stuck with my current unsafe implementation (version 5 in the code below, using type assertion as any
for spreading the extra input).
I guess I'm missing something about extending an interface and proper type assertion of the spread operator but I can't figure out what.
This is with TypeScript 3.0/3.1 in strict mode.
// data that we want to inject
// 'values' may not be null or undefined
interface InjectedData {
values: string;
}
// generic type of a query's result
interface QueryResult<T> {
// actual data provided by the query
// the injected values will be extracted from the payload
data?: {
error?: string;
loading: boolean;
payload?: [{ id: string; name: string }];
};
// rest of input data that should be propagated as-is
inputData: T;
}
// inject 'values' from a query result and propagate any extra input
// the extra input is explicitly forbidden to contain a 'values' field
type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
const injectValues = <P extends InjectedData>(
{ data, inputData }: QueryResult<Omit<P, InjectedData>>
): P => {
// 'values' is badly written and possibly undefined. TS detects it, which is good
// error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
// inputData are not propagated on yet
const v1: P = {
values: data && data.payload && data.payload.map(i => i.name)
};
// 'values' is fixed, but somehow cannot be assigned to P
// does the extension of OutData require actual extra fields?
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
// inputData are not propagated on yet
const v2: P = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// attempt to propagate inputData, but cannot be spread as-is from
// the Pick type
// error TS2698: Spread types may only be created from object types
const v3: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...inputData
};
// type-asserted inputData as object, but then assignment fails again
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
const v4: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// type-asserted inputData as any and it compiles...
const v5_1: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// ... but then values is allowed to be undefined again, so possible
// mistakes might not be caught
const v5_2: P = {
values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
...(inputData as any)
};
// without inputData, the inferred type of the RHS is {values: string}
const check1 = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// with inputData type-asserted as object, the inferred type of the RHS is {values: string}
const check2 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// with inputData type-asserted as any, the inferred type of the RHS is any
const check3 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// how to achieve both correct checking of 'values' and spreading of inputData?
return { values: [""] };
};
typescript apollo-client
add a comment |
I'm trying to define a data injector (in context: props
option of Apollo Client's graphql
HOC) and I have found out that my current implementation incorrectly allows some mistakes to go uncaught by TypeScript.
I have tried many variants as exposed below but I can't find the correct way to propagate extra input along with the injected data and I'm still stuck with my current unsafe implementation (version 5 in the code below, using type assertion as any
for spreading the extra input).
I guess I'm missing something about extending an interface and proper type assertion of the spread operator but I can't figure out what.
This is with TypeScript 3.0/3.1 in strict mode.
// data that we want to inject
// 'values' may not be null or undefined
interface InjectedData {
values: string;
}
// generic type of a query's result
interface QueryResult<T> {
// actual data provided by the query
// the injected values will be extracted from the payload
data?: {
error?: string;
loading: boolean;
payload?: [{ id: string; name: string }];
};
// rest of input data that should be propagated as-is
inputData: T;
}
// inject 'values' from a query result and propagate any extra input
// the extra input is explicitly forbidden to contain a 'values' field
type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
const injectValues = <P extends InjectedData>(
{ data, inputData }: QueryResult<Omit<P, InjectedData>>
): P => {
// 'values' is badly written and possibly undefined. TS detects it, which is good
// error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
// inputData are not propagated on yet
const v1: P = {
values: data && data.payload && data.payload.map(i => i.name)
};
// 'values' is fixed, but somehow cannot be assigned to P
// does the extension of OutData require actual extra fields?
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
// inputData are not propagated on yet
const v2: P = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// attempt to propagate inputData, but cannot be spread as-is from
// the Pick type
// error TS2698: Spread types may only be created from object types
const v3: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...inputData
};
// type-asserted inputData as object, but then assignment fails again
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
const v4: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// type-asserted inputData as any and it compiles...
const v5_1: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// ... but then values is allowed to be undefined again, so possible
// mistakes might not be caught
const v5_2: P = {
values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
...(inputData as any)
};
// without inputData, the inferred type of the RHS is {values: string}
const check1 = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// with inputData type-asserted as object, the inferred type of the RHS is {values: string}
const check2 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// with inputData type-asserted as any, the inferred type of the RHS is any
const check3 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// how to achieve both correct checking of 'values' and spreading of inputData?
return { values: [""] };
};
typescript apollo-client
add a comment |
I'm trying to define a data injector (in context: props
option of Apollo Client's graphql
HOC) and I have found out that my current implementation incorrectly allows some mistakes to go uncaught by TypeScript.
I have tried many variants as exposed below but I can't find the correct way to propagate extra input along with the injected data and I'm still stuck with my current unsafe implementation (version 5 in the code below, using type assertion as any
for spreading the extra input).
I guess I'm missing something about extending an interface and proper type assertion of the spread operator but I can't figure out what.
This is with TypeScript 3.0/3.1 in strict mode.
// data that we want to inject
// 'values' may not be null or undefined
interface InjectedData {
values: string;
}
// generic type of a query's result
interface QueryResult<T> {
// actual data provided by the query
// the injected values will be extracted from the payload
data?: {
error?: string;
loading: boolean;
payload?: [{ id: string; name: string }];
};
// rest of input data that should be propagated as-is
inputData: T;
}
// inject 'values' from a query result and propagate any extra input
// the extra input is explicitly forbidden to contain a 'values' field
type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
const injectValues = <P extends InjectedData>(
{ data, inputData }: QueryResult<Omit<P, InjectedData>>
): P => {
// 'values' is badly written and possibly undefined. TS detects it, which is good
// error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
// inputData are not propagated on yet
const v1: P = {
values: data && data.payload && data.payload.map(i => i.name)
};
// 'values' is fixed, but somehow cannot be assigned to P
// does the extension of OutData require actual extra fields?
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
// inputData are not propagated on yet
const v2: P = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// attempt to propagate inputData, but cannot be spread as-is from
// the Pick type
// error TS2698: Spread types may only be created from object types
const v3: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...inputData
};
// type-asserted inputData as object, but then assignment fails again
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
const v4: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// type-asserted inputData as any and it compiles...
const v5_1: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// ... but then values is allowed to be undefined again, so possible
// mistakes might not be caught
const v5_2: P = {
values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
...(inputData as any)
};
// without inputData, the inferred type of the RHS is {values: string}
const check1 = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// with inputData type-asserted as object, the inferred type of the RHS is {values: string}
const check2 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// with inputData type-asserted as any, the inferred type of the RHS is any
const check3 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// how to achieve both correct checking of 'values' and spreading of inputData?
return { values: [""] };
};
typescript apollo-client
I'm trying to define a data injector (in context: props
option of Apollo Client's graphql
HOC) and I have found out that my current implementation incorrectly allows some mistakes to go uncaught by TypeScript.
I have tried many variants as exposed below but I can't find the correct way to propagate extra input along with the injected data and I'm still stuck with my current unsafe implementation (version 5 in the code below, using type assertion as any
for spreading the extra input).
I guess I'm missing something about extending an interface and proper type assertion of the spread operator but I can't figure out what.
This is with TypeScript 3.0/3.1 in strict mode.
// data that we want to inject
// 'values' may not be null or undefined
interface InjectedData {
values: string;
}
// generic type of a query's result
interface QueryResult<T> {
// actual data provided by the query
// the injected values will be extracted from the payload
data?: {
error?: string;
loading: boolean;
payload?: [{ id: string; name: string }];
};
// rest of input data that should be propagated as-is
inputData: T;
}
// inject 'values' from a query result and propagate any extra input
// the extra input is explicitly forbidden to contain a 'values' field
type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
const injectValues = <P extends InjectedData>(
{ data, inputData }: QueryResult<Omit<P, InjectedData>>
): P => {
// 'values' is badly written and possibly undefined. TS detects it, which is good
// error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
// inputData are not propagated on yet
const v1: P = {
values: data && data.payload && data.payload.map(i => i.name)
};
// 'values' is fixed, but somehow cannot be assigned to P
// does the extension of OutData require actual extra fields?
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
// inputData are not propagated on yet
const v2: P = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// attempt to propagate inputData, but cannot be spread as-is from
// the Pick type
// error TS2698: Spread types may only be created from object types
const v3: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...inputData
};
// type-asserted inputData as object, but then assignment fails again
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
const v4: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// type-asserted inputData as any and it compiles...
const v5_1: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// ... but then values is allowed to be undefined again, so possible
// mistakes might not be caught
const v5_2: P = {
values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
...(inputData as any)
};
// without inputData, the inferred type of the RHS is {values: string}
const check1 = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// with inputData type-asserted as object, the inferred type of the RHS is {values: string}
const check2 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// with inputData type-asserted as any, the inferred type of the RHS is any
const check3 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// how to achieve both correct checking of 'values' and spreading of inputData?
return { values: [""] };
};
// data that we want to inject
// 'values' may not be null or undefined
interface InjectedData {
values: string;
}
// generic type of a query's result
interface QueryResult<T> {
// actual data provided by the query
// the injected values will be extracted from the payload
data?: {
error?: string;
loading: boolean;
payload?: [{ id: string; name: string }];
};
// rest of input data that should be propagated as-is
inputData: T;
}
// inject 'values' from a query result and propagate any extra input
// the extra input is explicitly forbidden to contain a 'values' field
type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
const injectValues = <P extends InjectedData>(
{ data, inputData }: QueryResult<Omit<P, InjectedData>>
): P => {
// 'values' is badly written and possibly undefined. TS detects it, which is good
// error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
// inputData are not propagated on yet
const v1: P = {
values: data && data.payload && data.payload.map(i => i.name)
};
// 'values' is fixed, but somehow cannot be assigned to P
// does the extension of OutData require actual extra fields?
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
// inputData are not propagated on yet
const v2: P = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// attempt to propagate inputData, but cannot be spread as-is from
// the Pick type
// error TS2698: Spread types may only be created from object types
const v3: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...inputData
};
// type-asserted inputData as object, but then assignment fails again
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
const v4: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// type-asserted inputData as any and it compiles...
const v5_1: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// ... but then values is allowed to be undefined again, so possible
// mistakes might not be caught
const v5_2: P = {
values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
...(inputData as any)
};
// without inputData, the inferred type of the RHS is {values: string}
const check1 = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// with inputData type-asserted as object, the inferred type of the RHS is {values: string}
const check2 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// with inputData type-asserted as any, the inferred type of the RHS is any
const check3 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// how to achieve both correct checking of 'values' and spreading of inputData?
return { values: [""] };
};
// data that we want to inject
// 'values' may not be null or undefined
interface InjectedData {
values: string;
}
// generic type of a query's result
interface QueryResult<T> {
// actual data provided by the query
// the injected values will be extracted from the payload
data?: {
error?: string;
loading: boolean;
payload?: [{ id: string; name: string }];
};
// rest of input data that should be propagated as-is
inputData: T;
}
// inject 'values' from a query result and propagate any extra input
// the extra input is explicitly forbidden to contain a 'values' field
type Omit<T, K> = Pick<T, Exclude<keyof T, keyof K>>;
const injectValues = <P extends InjectedData>(
{ data, inputData }: QueryResult<Omit<P, InjectedData>>
): P => {
// 'values' is badly written and possibly undefined. TS detects it, which is good
// error TS2322: Type 'string | undefined' is not assignable to type 'P["values"]'.
// inputData are not propagated on yet
const v1: P = {
values: data && data.payload && data.payload.map(i => i.name)
};
// 'values' is fixed, but somehow cannot be assigned to P
// does the extension of OutData require actual extra fields?
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
// inputData are not propagated on yet
const v2: P = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// attempt to propagate inputData, but cannot be spread as-is from
// the Pick type
// error TS2698: Spread types may only be created from object types
const v3: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...inputData
};
// type-asserted inputData as object, but then assignment fails again
// error TS2322: Type '{ values: string; }' is not assignable to type 'P'.
const v4: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// type-asserted inputData as any and it compiles...
const v5_1: P = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// ... but then values is allowed to be undefined again, so possible
// mistakes might not be caught
const v5_2: P = {
values: data && data.payload && data.payload.map(i => i.name), // undefined if data is undefined!
...(inputData as any)
};
// without inputData, the inferred type of the RHS is {values: string}
const check1 = {
values: (data && data.payload && data.payload.map(i => i.name)) ||
};
// with inputData type-asserted as object, the inferred type of the RHS is {values: string}
const check2 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as object)
};
// with inputData type-asserted as any, the inferred type of the RHS is any
const check3 = {
values: (data && data.payload && data.payload.map(i => i.name)) || ,
...(inputData as any)
};
// how to achieve both correct checking of 'values' and spreading of inputData?
return { values: [""] };
};
typescript apollo-client
typescript apollo-client
asked Nov 28 '18 at 9:31
Matthieu DazyMatthieu Dazy
13
13
add a comment |
add a comment |
0
active
oldest
votes
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%2f53516194%2fcannot-spread-extra-values-to-instance-of-extended-interface%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
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%2f53516194%2fcannot-spread-extra-values-to-instance-of-extended-interface%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