Playground: https://tsplay.dev/m3Gqqm
I’m trying to access deeply nested properties of my theme object based on input parameters, so I can provide a nice dev experience by having autocomplete.
But Typescript is losing inference when trying to access the nested properties inside the function body, when I try to access them like this. Why is that? And how can I solve it?
<code>const theme = {
typography: {
text: {
small: {
base: "",
mobile: "",
desktop: "",
},
medium: {
base: "",
mobile: "",
desktop: "",
},
},
heading: {
h1: {
base: "",
mobile: "",
desktop: "",
},
h2: {
base: "",
mobile: "",
desktop: "",
},
},
},
} as const;
type Theme = typeof theme;
function createVariant<T extends keyof Theme["typography"], S extends keyof Theme["typography"][T]>(
variantName: T,
variantValue: S,
) {
const breakpoints = theme.typography[variantName][variantValue];
const base = breakpoints.base; // error, why?
}
const result = createVariant("heading", "h2");
</code>
<code>const theme = {
typography: {
text: {
small: {
base: "",
mobile: "",
desktop: "",
},
medium: {
base: "",
mobile: "",
desktop: "",
},
},
heading: {
h1: {
base: "",
mobile: "",
desktop: "",
},
h2: {
base: "",
mobile: "",
desktop: "",
},
},
},
} as const;
type Theme = typeof theme;
function createVariant<T extends keyof Theme["typography"], S extends keyof Theme["typography"][T]>(
variantName: T,
variantValue: S,
) {
const breakpoints = theme.typography[variantName][variantValue];
const base = breakpoints.base; // error, why?
}
const result = createVariant("heading", "h2");
</code>
const theme = {
typography: {
text: {
small: {
base: "",
mobile: "",
desktop: "",
},
medium: {
base: "",
mobile: "",
desktop: "",
},
},
heading: {
h1: {
base: "",
mobile: "",
desktop: "",
},
h2: {
base: "",
mobile: "",
desktop: "",
},
},
},
} as const;
type Theme = typeof theme;
function createVariant<T extends keyof Theme["typography"], S extends keyof Theme["typography"][T]>(
variantName: T,
variantValue: S,
) {
const breakpoints = theme.typography[variantName][variantValue];
const base = breakpoints.base; // error, why?
}
const result = createVariant("heading", "h2");
When I explicitly annotate the theme object like this, it does know the available nested properties:
<code>type ThemeAnnotation = {
typography: {
text: {
[index: string]: Record<"base" | "mobile" | "desktop", string>;
},
heading: {
[index:string]: Record<"base" | "mobile" | "desktop", string>;
}
}
}
const theme:ThemeAnnotation = { ... }
</code>
<code>type ThemeAnnotation = {
typography: {
text: {
[index: string]: Record<"base" | "mobile" | "desktop", string>;
},
heading: {
[index:string]: Record<"base" | "mobile" | "desktop", string>;
}
}
}
const theme:ThemeAnnotation = { ... }
</code>
type ThemeAnnotation = {
typography: {
text: {
[index: string]: Record<"base" | "mobile" | "desktop", string>;
},
heading: {
[index:string]: Record<"base" | "mobile" | "desktop", string>;
}
}
}
const theme:ThemeAnnotation = { ... }
But that would prevent the autocompletion on the call site, because it can’t infer the available variantValue
.
Parameters, casting, generics. Didn’t work. And I don’t understand WHY this happens.