4

TypeScript is complaining

TS2322: Type '{ clientSecret: string; loader: string; }' is not assignable to type 'StripeElementsOptions'.   
  Types of property 'loader' are incompatible.     
    Type 'string' is not assignable to type '"always" | "auto" | "never"'. 

Where the object is defined as

const options = {
    clientSecret: paymentIntent.clientSecret,
    loader: "always",
}

The error goes away if I define it like this instead:

const options = {
    clientSecret: paymentIntent.clientSecret,
    loader: "always" as "always",
}

But surely I shouldn't have to do that. Am I doing something wrong or is TS being overly aggressive here?

enter image description here

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • If you inline the object creation you don't need it. But when you define the object separately the type gets widened to string because it could be changed. Here's an explanation of the same problem in a slightly different context: https://stackoverflow.com/a/62652353/3001761. – jonrsharpe May 22 '22 at 18:30
  • "But surely I shouldn't have to do that." With non-constant data, how should the compiler know at compile time that the string is one of those options? What if something else changed the string before the value got assigned? Static analysis is not easy. – Karl Knechtel May 22 '22 at 18:31
  • @KarlKnechtel Not easy, but sounds plausible that it could figure out there are no mutations between those 2 lines. I guess that makes sense though... if I *did* want to mutate it and it started complaining that "newstring" was not an "always", I'd probably be annoyed by that too. – mpen May 22 '22 at 18:32
  • 1
    If you ever try to write a compiler, you will quickly appreciate the virtue of only attempting things that you know are feasible in the general case. :) – Karl Knechtel May 22 '22 at 18:37

2 Answers2

8

Try to add as const to the options definiton:

const options = {
    clientSecret: paymentIntent.clientSecret,
    loader: "always",
} as const

TypeScript will often widen string literals to string. By using as const, we can stop TypeScript from widening literal types. This will make all properties readonly so TypeScript can make sure the values won't change afterwards.

You could also add the correct type to options:

const options: StripeElementsOptions = {
    clientSecret: paymentIntent.clientSecret,
    loader: "always",
}
Tobias S.
  • 21,159
  • 4
  • 27
  • 45
  • That works. Have to wait 8 mins to accept. Not sure I love that syntax, I try to avoid `as` as much as possible. Something like `readonly const` before `options` might have been nice. – mpen May 22 '22 at 18:35
2

If you don't explicitly type the variable, TypeScript will infer it as a string. You could type your options with the same type.

const options: { clientSecret: string; loader: "always" | "auto" | "never"; } = {
  clientSecret: paymentIntent.clientSecret,
  loader: "always",
}

Or simply.

const options: StripeElementsOptions = {
  clientSecret: paymentIntent.clientSecret,
  loader: "always",
}
axtck
  • 3,707
  • 2
  • 10
  • 26
  • 1
    Oh ya! Don't know why I didn't think of that. It has a type (`StripeElementsOptions`) so I don't even have to spell it out like that. – mpen May 22 '22 at 18:37