Contextual type can't be provided to a mapped type intersected with an object type
See original GitHub issueBug Report
🔎 Search Terms
intersection, mapped type, contextual type
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ
⏯ Playground Link
💻 Code
type Action<TEvent extends { type: string }> = (ev: TEvent) => void;
interface MachineConfig<TEvent extends { type: string }> {
schema: {
events: TEvent;
};
on?: {
[K in TEvent["type"]]?: Action<TEvent extends { type: K } ? TEvent : never>;
} & {
"*"?: Action<TEvent>;
};
}
declare function createMachine<TEvent extends { type: string }>(
config: MachineConfig<TEvent>
): void;
createMachine({
schema: {
events: {} as { type: "FOO" } | { type: "BAR" },
},
on: {
FOO: (ev) => {
ev.type; // should be 'FOO', but `ev` is typed implicitly as `any`
},
},
});
🙁 Actual behavior
An implicit any pop-ups when the contextual type could be, somewhat easily, provided.
🙂 Expected behavior
This should just work 😜 I know a workaround for this issue - the workaround is to use a single mapped type instead of an intersection and just “dispatch” to the correct value in the template~ part of the mapped type, like here. However, this is way less ergonomic than an intersection AND the mapped type is no longer homomorphic which could matter for some cases (well, the original mapped type here is not homomorphic either, but it could be)
I already have a draft PR open to fix this issue, here. I only need some help with the stuff mentioned in the comment here
Issue Analytics
- State:
- Created a year ago
- Comments:16 (7 by maintainers)
Top Results From Across the Web
Mastering TypeScript mapped types - LogRocket Blog
In this post, we'll cover mapped types in TypeScript, a real-world example of them, and utility types including Partial, Readonly, and Pick.
Read more >Creating a mapped type from an object without errors in ...
How do you create a fully mapped type, given the inability to create a mapped object without intermediate results in JavaScript?
Read more >Documentation - Type Inference - TypeScript
In TypeScript, there are several places where type inference is used to provide type information when there is no explicit type annotation.
Read more >Mapped Types - OpenAPI - A progressive Node.js framework
Hint The OmitType() function is imported from the @nestjs/swagger package. Intersection#. The IntersectionType() function combines two types into one new type ( ...
Read more >Advanced Types
Table of contents #. Intersection Types. Union Types. Type Guards and Differentiating Types. User-Defined Type Guards. Using type predicates; Using the in ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Lemme try an explanation 😄
We’re letting the user provide an object in the form
now, we want the return type of
actionPayload
to be the same assomething
up inPayloadAction
. But we cannot just add a generic<T>
somewhere to make sure that’s the case - because this object is not the only object being passed in, but one of many objects inside a config object:Now, TS has no syntax to allow for different ActionPayload types for
foo
andbar
while having those internally consistent (foo
only hasFooActionPayload
both onreducer
andprepare
andbar
only hasBarActionPayload
both onreducer
andprepare
).So what we do is that we let TypeScript infer this whole configuration object including all reducers (the “first pass”) and then, when we have that, we use that
ConfigObject
to restrict it against itself (the “second pass”) -ConfigObject extends Validated<ConfigObject>
whereValidated
is a generic that infersFooActionPayload
fromReturnType<ConfigObject['reducers']['foo']['prepare']>
and makes sure that the second argument toreducer
matches that type.It’s amazing that we could do something like that in the first place, but it’s also pretty necessary here to make the api work in a type-safe manner.
Good point, here is a test case: Playground link