counterbeing
counterbeing

Reputation: 2791

How to know which type to use in TypeScript?

The Main Question

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.

Example Code

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.

Working as expected

const printColor = () => {
  const theme = createMuiTheme();
  const color = theme.palette.primary['main'];
  console.log(color);
};
printColor(); // prints #3f51b5 to the console

The failing example

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'.

Further Thoughts

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?

Run The Code

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

Answers (1)

jcalz
jcalz

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:

    IntelliSense

    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!

Stackblitz link to code

Upvotes: 2

Related Questions