Reputation: 2791
I seem to keep running into this in different forms in typescript. Sometimes a function, or some kind of object requires a specific interface, and I'm looking for a general set of steps to follow to figure out what those interfaces are.
I'm trying to write a function that will iterate over the various colors of a Material-UI theme. I'm having trouble even accessing an individual one once I start passing parameters to do the job.
const printColor = () => {
const theme = createMuiTheme();
const color = theme.palette.primary['main'];
console.log(color);
};
printColor(); // prints #3f51b5 to the console
const printColor = (key: string) => {
const theme = createMuiTheme();
const color = theme.palette.primary[key];
console.log(color);
};
printColor('main');
The error:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'PaletteColor'.
No index signature with a parameter of type 'string' was found on type 'PaletteColor'.
It seems as though I need to be adding some kind of type for the parameter key
? I've been hunting around to try to figure out what type that would be. Why does this work when I access it directly as a string, and not when I pass it as a parameter?
Is there some way that you quickly determine which type to use when you run into these sorts of type errors?
As requested here's a link to play with the code yourself. https://stackblitz.com/edit/typescript-6rntqm
The code runs here, but only gives a warning. I think my tsconfig is just set to fail, but I'm trying to learn how to troubleshoot these things myself.
Upvotes: 2
Views: 368
Reputation: 328097
So the "right" answer for this probably looks like
import { PaletteColor } from "@material-ui/core/styles/createPalette";
and then later
const noLongerFails = (key: keyof PaletteColor) => {
const theme = createMuiTheme();
const color = theme.palette.primary[key];
console.log(color);
};
Note that we want key
to be one of the keys of PaletteColor
, so we use the keyof
type operator to turn an object type into a union of its keys.
Tracking down the proper PaletteColor
type and where it's exported is a bit obnoxious (maybe someone has a better solution than what I did), but it looks like this:
hover over theme.palette.primary
in your IDE and see what IntelliSense says about its type:
In this case it says (property) Palette.primary: PaletteColor
. So if we're lucky, there's a type exported somewhere called PaletteColor
.
If you have the relevant node_modules
info for material-ui installed locally you can search it for that type. If not, you can always look at the source code and search that. Fortunately there is one hit and you can see it is exported as an interface
in styles/createPalette.d.ts
.
We import from there and it works!
If we couldn't find an appropriately exported type, we could instead start using TypeScript's type queries to tease the required type from the things we can import:
type MyPaletteColor = ReturnType<typeof createMuiTheme>["palette"]["primary"];
Let's do this step by step. Given a named value like createMuiTheme
, we can ask TypeScript for its type by using the typeof
type query operator. (Not to be confused with the runtime typeof
operator. See this answer for a long explanation of the difference between type names and value names) So typeof createMuiTheme
is a function type:
type CreateMuiThemeType = typeof createMuiTheme;
// type CreateMuiThemeType = (options?: ThemeOptions | undefined, ...args: object[]) => Theme;
Now that function returns a Theme
. To get ahold of that type (pretending we haven't already imported it) we can use the ReturnType
utility type:
type MyTheme = ReturnType<CreateMuiThemeType>;
// type MyTheme = Theme;
Finally, we know that the type we're looking for is the primary
property of the palette
property of Theme
. For this we can use lookup types using indexed access notation:
type MyPalette = MyTheme["palette"];
// type MyPalette = PaletteColor;
type MyPaletteColorAlso = MyPalette["primary"];
// type MyPaletteColorAlso = PaletteColor;
We can collapse those into a single line as in MyPaletteColor
above. And oncewe have our own definition of MyPaletteColor
we can use it instead of PaletteColor
:
const alsoWorks = (key: keyof MyPaletteColor) => {
const theme = createMuiTheme();
const color = theme.palette.primary[key];
console.log(color);
};
and this also works.
Okay, hope that helps; good luck!
Upvotes: 2