Home > Back-end >  Typescript Bug: Extending An Inferred Type Prevents TS Linting Errors (Playground Example Provided)
Typescript Bug: Extending An Inferred Type Prevents TS Linting Errors (Playground Example Provided)

Time:10-13

I have a function called myStyles, which takes a single argument. This argument is an object containing a baseStyles object and a styleVariants object. It simply returns the styleVariants object.

function myStyles<StyleVariants extends StyleVariantsStructure>(params: {
  baseStyles: AllowedStyles;
  styleVariants: StyleVariants;
}): StyleVariants {
  return params.styleVariants;
}

It is called like so:

myStyles({
  baseStyles: {
    color: 'red',
    backgroundColor: 'red',
  },
  styleVariants: {
    color: {
      red: {
        color: 'red',
        backgroundColor: 'red',
      },
    },
  },
});

The baseStyles object has the type AllowedStyles, which determines which css properties and values we allow. This works perfectly and we get the correct TS errors if the CSS properties or values are misspelled when calling the myStyles function e.g.

type AllowedStyles = {
  color?: 'red';
  backgroundColor?: 'red';
};

The styleVariants object has the type StyleVariantsStructure. This essentially determines that it should accept one or many nested objects that contain the AllowedStyles e.g.

type StyleVariantsStructure = {
  [k: string]: {
    [k: string]: AllowedStyles;
  };
};

In the myStyles function, we use type inference for the styleVariants param object. Meaning, we automatically get the exact type of the styleVariants passed in to the myStyles function. We then use the styleVariants type as the return type for the function. This is required for us, because we need to know the exact return type of myStyles. Here's our function again:

function myStyles<StyleVariants extends StyleVariantsStructure>(params: {
  baseStyles: AllowedStyles;
  styleVariants: StyleVariants;
}): StyleVariants {
  return params.styleVariants;
}

And here's an example of the type inference return type, taken from a enter image description here

The problem

As you can see, I need to both restrict things being passed in the styleVariants object, using the StyleVariantsStructure type e.g. we must only accept AllowedStyles. However, we also need to use the inferred type from styleVariants for the return type of the myStyles function. I have attempted to solve this by using StyleVariants extends StyleVariantsStructure. This works somewhat, but some Typescript errors aren't highlighted in the styleVariants argument object when calling myStyles.

For example, if color is misspelled, we get no TS error:

enter image description here

However, I do get type suggestions:

enter image description here

And, I do get errors if the value is misspelled:

enter image description here

Essentially, to me, it seems like I need to find an alternate way to use type inference for the styleVariants object, whilst also restricting it using the StyleVariantsStructure type. Any help on this would be much appreciated.

Minimal demo of the issue in Typescript playground

CodePudding user response:

Strictly speaking, this looks correct, but TS needs to be coaxed into checking it. For example, wrapping the call in the playground with console.log() generates the typechecking you want.

TS is imperfect as a checker, so it can take some finagling for it to detect the "right" solution in highly complex situations - which this is!

CodePudding user response:

The problem seems to be a limitation of Typescript more than anything else. A workaround, which is a complete solution for us, that I found is the following:

function myStyles<StyleVariants>(params: {
  baseStyles: AllowedStyles;
  styleVariantKeys: StyleVariants;
  styleVariants: StyleVariantsStructure;
}): StyleVariants {
  return params.styleVariantKeys;
}
  1. Add styleVariantsKey object to the myStyles function params
  2. Infer the type from that
  3. Give styleVariants the StyleVariantsStructure type

This allows you to get type checking as intended, and allows you to return the inferred type from styleVariantsKey. But of course it means you have to have two of the same object, which in our case isn't too much of a problem because we don't actually need the nested styles object to be returned by myStyles, just the variant names. So our function call looks like this:

myStyles({
  baseStyles: {
    color: 'red',
    backgroundColor: 'red',
  },
  styleVariantKeys: {
    color: ["red"],
  },
  styleVariants: {
    color: {
      red: {
        color: 'red',
        backgroundColor: 'red',
      },
    },
  },
});
  • Related