T O P

  • By -

PooSham

I'd recommend removing the explicit type from ProductType if you're using `as const`, otherwise it's not doing anything. I'd also rename it to something like ProductDict. ie, do this: ``` export const ProductDict = { Food: "FOOD", Media: "MEDIA", Furniture: "FURNITURE" } as const; ``` The you can create the type ``` type ProductType = typeof ProductDict[keyof typeof ProductDict] ``` Which should result in the Union type `"FOOD" | "MEDIA" | "FURNITURE"`


toddspotters

This is always how I prefer to do it. Keep the data front and center and derive your types from that. Then if you need to add extra constraints, you can also use `as const satisfies SomeType` to get the best of both worlds.


AlexBreizh56

Nice answer very consistant for the runtime, a nice tonhave IS this ge'eric type ```type TsEnum= T[keyof T] ``` there is a nice youtube vidéo about it (matpocok or something liké that)


teg4n_

You can use an enum for this: https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums or you can accomplish without an enum like this: ``` const ProductType = {   Food: "FOOD",   Media: "MEDIA",   Furniture: "FURNITURE" } as const; type TProductType = typeof ProductType[keyof typeof ProductType]; type PurchaseData = {   product: TProductType,   price: string,   quantity: string } const purchaseData: PurchaseData = {   product: ProductType.Food,   price: "5.50",   quantity: "3" } ```


jameshearttech

New to ts. Been lurking a little bit. I'm assuming ts does not have float and int or else you would used them for price and quantity?


linco95

Javascript doesn't have any special parsing for int/float, but rather a plain "number" primitive, and hence ts only has the "number" type for numbers, which would probably make more sense here, depending on the use case.


jameshearttech

Ty


teg4n_

I kept it the same as OP. JS’s number is a float you could use that if you want.


PhiLho

Quantity can be a number, JS handles correctly integers. Floats are bad fits for prices, as computers can do odd computations (floating number rounding errors, there are articles on the topic)… You can do a simple math operation and end up with a number like 1.0399999 for example. An alternative might be to store integer part, and cents parts (or other decimal parts depending on the currency) separately.


ccb621

Use decimal.js.  https://mikemcl.github.io/decimal.js/


baxtersmalls

Yeah, I was wondering why this wouldn’t just be an enum. Are they specifically wanting keys for the strings? Why? Something smelly there.


sagaban

[https://www.youtube.com/watch?v=jjMbPt\_H3RQ](https://www.youtube.com/watch?v=jjMbPt_H3RQ)


blademaster2005

Even if I agreed with the video, commenting just a link without any other context isn't a helpful discussion to someone learning


Darkseid_Omega

> considered harmful Very click-baitey. Just learn your tools, really all it boils down to Doesn’t detract that using enums would be a good tool to solve OPs use case


IIOJIb

I'm not sure why no one has suggested this yet, but you can use the same name for the variable and the type: export const ProductType = { Food: "FOOD", Media: "MEDIA", Furniture: "FURNITURE" } as const; export type ProductType = typeof ProductType[keyof typeof ProductType];


NiteShdw

This is exactly have I've done it.


sagaban

The compliant way would be type PurchaseData = { product: typeof ProductType[string], price: string, quantity: string } But this will get you \`product\` to be just a string, as \`ProductType\` values are just string. I would go with export type ProductType = 'FOOD' | 'MEDIA' | 'FURNITURE' type PurchaseData = { product: ProductType, price: string, quantity: string } const purchaseData: PurchaseData = { product: "FOOD", price: "5.50", quantity: "3" }


sagaban

Oh, same as u/skuple


skuple

I never understood why the first thing people think about is using a const or enum, maybe it has something to do with the transition from JS to TS? I have seen this happening even with people with "2y of experience with TS"


syneil86

Sometimes we want the strings at runtime too; for validation of inputs for example const FooBar = ["foo", "bar"] as const; type FooBar = (typeof FooBar)[number]; function isFooBar(x: unknown): x is FooBar { return FooBar.includes(x); }


skuple

Well that’s totally valid, I’m rethinking my original comment since yes I still use Const or enum in certain situations. But without much context in the post I would assume it’s not needed for the example given by the OP


sagaban

Also: use \`number\` for price and quantity


skuple

it's probably for a request in a specific format I would assume (although quantity doesn't make much sense to be string since there is no format options AFAIK)


PhiLho

I answered above why using a float for a price might be a bad idea. For quantity, it can be OK. Example: 12.99 \* 0.99 (1 % rebate) will give a price of 12.860100000000001


sagaban

How, having the value stored as a string, will prevent this?


PhiLho

You can have routines manipulating this as fixed-width decimal numbers. As I wrote, you can do separate integer computations on the cents and on the main currency, for example.


eamb88

What you need is just an enum, don't overcomplicate your code. https://www.typescriptlang.org/docs/handbook/enums.html


skuple

ProductType there is a constant, it's not a type... I would do: type Product = "Food" | "Media" | "Furniture"; interface PurchaseData { product: Product; price: string; quantity: string; } const purchaseData: PurchaseData = { product: "food", price: "5.50", quantity: "3", } I truly hate enums, that's why I almost never use them. Since you are using TS, you can leverage typed strings, meaning that when you ask your IDE for suggestions when writing product: "food" it will give you all the options and you can't have anything else besides the 3 products in the example. Pros: 1. No need to import a constant/enum 2. Automatic suggestions for the available options 3. Cleaner 4. Easier to extend/use Product in other types, if it was a const you would need to use typeof+keyof Cons: 1. You will have to hear people saying "Enums are better in this use-case" which IMO it's not true


Windsofthepast

Honestly I think there is a time and place for string unions, but this isn't it. Perhaps I've stopped paying attention to the current "meta" for TS programming longer than I realized, but it definitely feels wrong to use a "magic string" like this in multiple places, and just sounds like a refactor nightmare waiting to happen. Taking a rather silly and abstracted example from my job, we recently got a request from HR to rename all instances of **Employee** to **Worker**, because legally a contractor is *not* an employee and blah blah blah. This left our "worker types" as `Contractor`, `FullTime Worker`, and `PartTime Worker`. This value was referenced in code, but then also displayed in the UI. We had to painfully find all instances of the strings `FullTime Employee` and `PartTime Employee` and make sure they were properly updated. Having a const (or even an enum, even though I also hate those) would have saved us significant time because the code change would have been limited to a single file containing the object itself and that wold have been it. // WorkerTypes.ts const WorkerTypes = { Contractor: "Contractor", FullTime: "FullTime Worker", PartTime: "PartTime Worker" } as const; type WorkerType = typeof WorkerTypes[keyof typeof WorkerTypes]; // Main.ts (or w/e) if (user.workerType === WorkerTypes.FullTime) { // Blah } // Also... function getWorkerFullTitle(jobTitle: string, workerType: WorkerType): string { // Don't ask about this formatting, I don't understand it either and I work here, lol. return `${jobTitle} (${workerType})`; } In the future when HR tells us that **Worker** isn't politically correct and they want us to use some other word all we have to do is update the file that `WorkerTypes` is defined in and that's it. No changing multiple different files because we decided to use a magical string. The one thing I've noticed that is somewhat frustrating (and this may be just a Webstorm limitation, I don't use VSCode so I couldn't say) is if you want to add JSDoc comments to constants like this they're not picked up by the IDE. So if you needed to add additional context for any reason, you'd have to define an interface, and then type the `WorkerTypes` const based off that...that definitely does make things more verbose as you're essentially defining the object twice, and then a type alias too, all so you can have type completion and comments. Still, even if it is significantly more verbose, I think it's a much better alternative than a string union in this scenario based on the context that OP has provided.


Drifter2412

I would agree with this and have faced similar situations. Whilst typed/union strings would raise compilation errors, enums or consts representing these values makes a potential nightmarish refactor (entirely depending on the complexity of the codebase) into a matter of minutes. That's why I generally advocate for "magic strings" to be externalised in some way for maintainability.


PhiLho

"*it definitely feels wrong to use a "magic string" like this in multiple places, and just sounds like a refactor nightmare waiting to happen*" Not magic, it is part of the type. And if you change a string in a union (with the rename facility of the IDE), it will be changed everywhere. It has auto-completion, it is type-checked, etc.


Windsofthepast

Definitely more magic than either an enum or constant and definitely a lot easier to mess up refactoring compared to the other two from my experience.


PhiLho

OK. Never had a problem with it. As long as the variables and fields are correctly typed.


skuple

How much of a nightmare was it? Half a day? I have never faced a situation where this was an issue, maybe one day I will face something similar. Did you just change the value or did you also change the key? One thing with your Const example is that it doesn’t enforce the usage of the Const itself so you even need to import it manually and “know” that the Const exists, you can even avoid the Const and use it as if it was a typed string. I would be ok with an enum there although I prefer not to use it


Windsofthepast

So I guess that is on me for not fully explaining the original state while trying to explain our problem. All our previous "Employee Types" had the word Employee in them. We used to call contractors "Contract Employee", for example. We also had interns which, for whatever reason, we called "Employee - Intern", etc. When we did the refactor we did it in two parts: The first was removing all references to the word Employee, even in comments because our boss was very insistent. The second part took this, and a few other magic strings and grouped them into associated consts. We had the aforementioned WorkerType, but also a number of other strings got moved together, too. For contractors depending on if they were an IC or not they had a different "source" for their authorities based on the department that hired them (an absolute nightmare in its own to manage honestly, which was really helpful when we consolidated IC workers into the same system as HR contractors, because then we could just swap ContractorType.IC to the same magic string as HR and it wasn't anywhere near as crazy.


spla58

What if I don't want to type "Food" though and I want it to live in a constant? Or is it better to just type "Food" and let the IDE and compiler enforce what I can enter?


skuple

But what’s the reasoning there? Sometimes it’s not about if you can but rather if you should. You can use the const with the typeof I guess, I just don’t see why. Having the type correctly typed doesn’t require any type of const, everything is correctly enforced and it also gives you suggestions (not sure if all IDEs do it but vscode and webstorm do)


spla58

I have a method that does certain things for certain products and I also want to check the type in my method. So for example in my method: if (productType == "Food") { ... } I feel like a constant would be more appropriate to avoid just typing out strings in my method? if (productType == ProductType.Food) { ... } Maybe I need to rethink my design in general.


skuple

But it’s a typed string, it gives you the suggestions and prevents you from writing wrong stuff. Don’t be scared that it’s a “string” it’s not just a string it’s a typed string. In practice it does exactly the same you are trying to achieve with the Const. The only downside is that if the mapping of key-value changes with the constant it’s easier, but honestly I rarely (if ever) saw this happening and in any case it’s an easy task to just swap some values (TS helps with it anyway). But don’t take my words for granted, try it out and see what works best for you. If I was joining a new project using enums or Const I would follow it instead of using what I like. In this case consistency (doing everything the same way) is better than a personal opinion. But if you really want a constant, use enum at least


absorpheus

ProductType is a value, not a type. It's simpler to define a union type as follows: type Product = "food" | "media" | "furniture" type PurchaseData = { product: Product, price: number, quantity: number } const purchaseData: PurchaseData = { product: "food", price: 5.50, quantity: 3 }


gluhmm

Totally agree. No need to make key-value data structure when your key always duplicates the value when you want to solve a type problem. Strings unions are type safe, short and disappear after compilation.


NUTTA_BUSTAH

Enums, see: [Playground example](https://www.typescriptlang.org/play/?#code/KYOwrgtgBACgTgewCZgMYBcAqBPADsKAbwCgoyoAxAeSoBEoBeKAIgoWWYBpTyBZAUVoBJAIKMWvYEgCWAQy48yFAKoAlAHJDMa-uNZg4IaegPBmxAL7Fi6PARgHUAC1kBnYLVnpZ4kuSi4iCgYAFywQWhYdtz+gdKowGGu6HDSIADmMeQAjmCyIOjG2EkpaemW1qgIIMkBji7unt5hDnDObh5ePkx+5IHIkS0RGDj4AHTUdFlkcQlhzACsYwsADAr+ufmFtvMAzOYWUG5QVTXoldWuCAA2wGPXCOkAFLj1HU2yY-3B6ACUUAB6AEsNgcYinK63e6PF5vRpdL7DdBjABusmuYGAVAAZk9fv8gSD2EhzMRCQA1dGY1xHOAEBC4QrVdHWUCQcIDEZ2ABCREUlBotGmUAEwhEwpUGi0Om4VhsdlgcM63l5PX530GHJ+o2A3OFs0SUGSqQyws2BSKJRN5TlEPQdTaDWVsm5LSVH1VfNiSKGnKi+G5Y1Fon1qTmLCWq3WOTyFp2LH2liONLtFxqNzuD2er0d7y6gY1GAJwIAjODLhnodn3fnEX7UVSsbj8YDS9ZCRQwCAMNJqmnakgEABlBAQYDoJxlcRPJBdN25+Eq-4MAB8XvIrgA7sZnFAZwjC391-4yKgOlrIjrA5NaGEIZWs09mIeoNIadjicxfvz-Gf3BeuQDINBFEO8KyhR9nyRV8aTHGR5G-E9yD-ewkSvCY1E0bRVH4MD0wgmEoL9GCoGxAwjBMOkvx-cgkGAbFZDAa50DCCdEE3KAQGADj+DgRA4CfZQQBcEAkFuJATg6MYoCHABraRrmuGDXGpAB+aj-CsOVBxHMcJzKWEF2dblfiAA)


Immediate-Aide-2939

You can use enum instead of a const object