This is a fine line. If you get a white screen of death you know something is wrong. If the first name is missing it may mean other things are missing and the app is in a bad state. That means the user could lose any work they try to do, which is a cardinal sin for an app to commit.
Context matters a lot. If it's Spotify and my music won't play is a lot different than I filled a form to send my rent check and it silently failed.
I keep wondering about a type system where you can say something like "A number greater than 4" or "A string of length greater than 0" or "A number greater than the value of $othernum". If you could do that, you could push so much of this "coping" logic to only the very edge of your application that validates inputs, and then proceed with lovely typesafe values.
There is some ceremony around it, but when you do the basic plumbing it's invaluable to import NonEmptyString100 schema to define a string between 1 and 100 chars, and have parsing and error handling for free anywhere, from your APIs to your forms.
This also implies that you cannot pass any string to an API expecting NonEmptyString100, it has to be that exact thing.
Or in e-commerce where we have complex pricing formulas (items like showers that need to be custom built for the customer) need to be configured and priced with some very complex formulas, often market dependent, and types just sing and avoid you multiplying a generic number (which will need to be a positive € schema) with a swiss VAT rate or to do really any operation if the API requires the branded version.
Typescript is an incredibly powerful language, it is kinda limited by its verbose syntax and JS compatibility but the things that you can express in Typescript I haven't seen in any other language.
> Personally, I've come to see this ubiquitous string of symbols, ?? "", as the JS equivalent to .unwrap() in Rust
It's funny you bring this up because people opposed to `.unwrap()` usually mention methods like `.unwrap_or` as a "better" alternative, and that's exactly the equivalent of `??` in Rust.
Semantically the two are kind of opposite; the similarity is that they're the lowest-syntax way in their respective languages to ignore the possibility of a missing value, and so get overused in situations where that possibility should not be ignored, leading to bugs.
I'm not familiar with Zod, but one thing that is quite important on the user end is to produce multiple (but, per policy, not infinite) error messages before giving up. That is, list all environment variables that need to be set, not just whichever one the code happens to be first.
This could be implemented with `??`, something like: `process.env.NODE_ENV ?? deferred_error(/temporary fallback/'', 'NODE_ENV not set')`, but is probably best done via a dedicated wrapper.
It seems Rust's unwrap is the exact opposite of ?? "". It throws an error instead of using a fallback value, which is exactly what the author suggests instead of using ?? "".
Author here. I believe I did a poor job of explaining what I meant by this sentence in the article. Sorry about that.
As you, and many others, have pointed out, `?? ""` does not do what `.unwrap` does. `unwrap_or_default` would have been a better comparison for what it actually does. What I tried, and failed, to communicate, was that both can be used to "ignore" the unwanted state, `None` or `undefined`.
I guess the rust equivalent to what I would like to see is `nullable_var?`, and not unwrap as that will panic. That would be equivalent to the `?? throw new Error` feature mentioned further up in the comments.
> In Rust, however, you're forced to reason about the "seriousness" of calling .unwrap() as it could terminate your program. In TS you're not faced with the same consequences.
Early errors are good, but I think the author overstates the importance of filtering out empty strings
---
I disagree that erroring out when the app doesn't have ALL the data is the best course of action. I imagine it depends a bit on the domain, but for most apps I've worked on it's better to show someone partial or empty data than an error.
Often the decision of what to do when the data is missing is best left up the the display component. "Don't show this div if string is empty", or show placeholder value.
---
Flip side, when writing an under the hood function, it often doesn't matter if the data is empty or not.
If I'm aggregating a list of fooBars, do I care if fooBarDisplayName is empty or not? When I'm writing tests and building test-data, needing to add a value for each string field is cumbersome.
Sometimes a field really really matters and should throw (an empty string ID is bad), but those are the exception and not the rule.
I cannot tell if the author would agree with this, but here’s my take:
There’s nothing wrong with using the ?? operator, even liberally. But in cases where 1) you don’t think the left side should be undefinable at all or 2) the right side is also an invalid value, you’re better off with an if-statement and handling the invalid case explicitly.
But I’ve used ?? thousands of times in my code in ways unrelated to 1) and 2).
Trivial example:
const numElements = arr?.length ?? 0
IMO this is fine if it’s not an important distinction between an array not existing and the array being empty, and gives you an easier to work with number type.
I definitely do agree with this! It's a very helpful operator in a lot of cases. I think the two cases you point out are prime examples of cases where I would prefer _not_ to encounter them.
Is this a weakness in the type definition? If we're sure the value cannot be undefined, then why doesn't the type reflect that? Why not cast to a non-undefined type as soon as we're sure (and throw if it is not)? At least that would document our belief in the value state.
If your type definitions are airtight then this problem doesn't come up, but sometimes, for whatever reason which may not be entirely within your control, they aren't. Narrowing and failing early is precisely what the article advises doing.
The problem I have when writing JS/TS is that asserting non-null adds verbosity:
Consider the author’s proposed alternative to ‘process.env.MY_ENV_VAR ?? “”’:
if (process.env.MY_ENV_VAR === undefined) {
throw new Error("MY_ENV_VAR is not set")
}
That’s 3 lines and can’t be in expression position.
There’s a terser way: define an ‘unwrap’ function, used like ‘unwrap(process.env.MY_ENV_VAR)’. But it’s still verbose and doesn’t compose (Rust’s ‘unwrap’ is also criticized for its verbosity).
TypeScript’s ‘!’ would work, except that operator assumes the type is non-null, i.e. it’s (yet another TypeScript coercion that) isn’t actually checked at runtime so is discouraged. Swift and Kotlin even have a similar operator that is checked. At least it’s just syntax so easy to lint…
It’s a feature of the language that’s totally fine to use as much as needed. it’s not a quick n dirty fix.
Don’t listen to those opinionated purists that don’t like certain styles for some reason (probably because they didn’t have that operator when growing up and think it’s a hack)
The user is an entity from the DB, where the email field is nullable, which makes perfect sense. The input component only accepts a string for the defaultValue prop. So you coalesce the possible null to a string.
Author here. It is. I did a poor job explaining what I meant by this. I tried to elaborate https://news.ycombinator.com/item?id=46089466. I'll also update the article to reflect this:) Thanks for pointing it out:)
The issue isn't the nullish coalescing, but trying to treat a `string | null` as a string and just giving it a non-sense value you hope you'll never use.
You could have the same issue with `if` or `user?.name!`.
Basically seems the issue is the `""`. Maybe it can be `null` and it should be `NA`, or maybe it shouldn't be `null` and should have been handled upstream.
This post and everyone defending this is absolutely crazy
Is this a kink? You like being on call?
You think your performance review is going to be better because you fixed a fire over a holiday weekend or will it be worse when the post mortem said it crashed because you didn’t handle the event bus failing to return firstName
ryanpetrich|3 months ago
h1fra|3 months ago
renewiltord|3 months ago
> By doing this, you're opening up for the possibility of showing a UI where the name is "". Is that really a valid state for the UI?
But as a user, if I get a white screen of death instead of your program saying “Hi, , you have 3 videos on your watchlist” I am going to flip out.
Programmers know this so they do that so that something irrelevant like my name doesn’t prevent me from actually streaming my movies.
noman-land|3 months ago
Context matters a lot. If it's Spotify and my music won't play is a lot different than I filled a form to send my rent check and it silently failed.
mystifyingpoi|3 months ago
No one suggests hard crashing the app in a way that makes the screen white. There are better ways. At least, send the error log to telemetry.
pverheggen|3 months ago
polishdude20|3 months ago
robertlagrant|3 months ago
epolanski|3 months ago
https://effect.website/docs/schema/advanced-usage/#branded-t...
There is some ceremony around it, but when you do the basic plumbing it's invaluable to import NonEmptyString100 schema to define a string between 1 and 100 chars, and have parsing and error handling for free anywhere, from your APIs to your forms.
This also implies that you cannot pass any string to an API expecting NonEmptyString100, it has to be that exact thing.
Or in e-commerce where we have complex pricing formulas (items like showers that need to be custom built for the customer) need to be configured and priced with some very complex formulas, often market dependent, and types just sing and avoid you multiplying a generic number (which will need to be a positive € schema) with a swiss VAT rate or to do really any operation if the API requires the branded version.
Typescript is an incredibly powerful language, it is kinda limited by its verbose syntax and JS compatibility but the things that you can express in Typescript I haven't seen in any other language.
raincole|3 months ago
davidmurdoch|3 months ago
SkiFire13|3 months ago
It's funny you bring this up because people opposed to `.unwrap()` usually mention methods like `.unwrap_or` as a "better" alternative, and that's exactly the equivalent of `??` in Rust.
ameliaquining|3 months ago
superjose|3 months ago
I learned from a friend to use Zod to check for process.env. I refined it a bit and got:
```
const EnvSchema = z.object({
});export type AlertDownEnv = z.infer<typeof EnvSchema>;
export function getEnvironments(env: Record<string, string>): AlertDownEnv { return EnvSchema.parse(env); }
```
Then you can:
```
const env = getEnvironments(process.env);
```
`env` will be fully typed!
Definitely, I need to do some improvements in my frontend logic!
o11c|3 months ago
This could be implemented with `??`, something like: `process.env.NODE_ENV ?? deferred_error(/temporary fallback/'', 'NODE_ENV not set')`, but is probably best done via a dedicated wrapper.
cloudflare728|3 months ago
I am quite surprised people here doesn't know how to validate data in runtime. The author completely mixing Typescript with runtime behavior.
a?.b?.c?.() or var ?? something have well documented use cases and it's not what the author is thinking.
cypressious|3 months ago
fred_|3 months ago
As you, and many others, have pointed out, `?? ""` does not do what `.unwrap` does. `unwrap_or_default` would have been a better comparison for what it actually does. What I tried, and failed, to communicate, was that both can be used to "ignore" the unwanted state, `None` or `undefined`.
I guess the rust equivalent to what I would like to see is `nullable_var?`, and not unwrap as that will panic. That would be equivalent to the `?? throw new Error` feature mentioned further up in the comments.
robertlagrant|3 months ago
> In Rust, however, you're forced to reason about the "seriousness" of calling .unwrap() as it could terminate your program. In TS you're not faced with the same consequences.
paulddraper|3 months ago
unwrap() is the error.
unwrap_or() is the fallback.
britch|3 months ago
---
I disagree that erroring out when the app doesn't have ALL the data is the best course of action. I imagine it depends a bit on the domain, but for most apps I've worked on it's better to show someone partial or empty data than an error.
Often the decision of what to do when the data is missing is best left up the the display component. "Don't show this div if string is empty", or show placeholder value.
---
Flip side, when writing an under the hood function, it often doesn't matter if the data is empty or not.
If I'm aggregating a list of fooBars, do I care if fooBarDisplayName is empty or not? When I'm writing tests and building test-data, needing to add a value for each string field is cumbersome.
Sometimes a field really really matters and should throw (an empty string ID is bad), but those are the exception and not the rule.
kennywinker|3 months ago
It’s the difference between:
if (fooBarDisplayName) { show div }
And:
if (foobarDisplayName && foobarDisplayName.length > 0) { show div }
Ultimately, type systems aren’t telling you what types you have to use where - they’re giving you tools to define and constrain what types are ok.
jjj123|3 months ago
There’s nothing wrong with using the ?? operator, even liberally. But in cases where 1) you don’t think the left side should be undefinable at all or 2) the right side is also an invalid value, you’re better off with an if-statement and handling the invalid case explicitly.
But I’ve used ?? thousands of times in my code in ways unrelated to 1) and 2).
Trivial example:
const numElements = arr?.length ?? 0
IMO this is fine if it’s not an important distinction between an array not existing and the array being empty, and gives you an easier to work with number type.
fred_|3 months ago
GMoromisato|3 months ago
I may not understand.
ameliaquining|3 months ago
raincole|3 months ago
> Why not cast to a non-undefined type as soon as we're sure (and throw if it is not)
This is exactly what the OP author suggests.
armchairhacker|3 months ago
Consider the author’s proposed alternative to ‘process.env.MY_ENV_VAR ?? “”’:
That’s 3 lines and can’t be in expression position.There’s a terser way: define an ‘unwrap’ function, used like ‘unwrap(process.env.MY_ENV_VAR)’. But it’s still verbose and doesn’t compose (Rust’s ‘unwrap’ is also criticized for its verbosity).
TypeScript’s ‘!’ would work, except that operator assumes the type is non-null, i.e. it’s (yet another TypeScript coercion that) isn’t actually checked at runtime so is discouraged. Swift and Kotlin even have a similar operator that is checked. At least it’s just syntax so easy to lint…
d--b|3 months ago
Don’t listen to those opinionated purists that don’t like certain styles for some reason (probably because they didn’t have that operator when growing up and think it’s a hack)
flufluflufluffy|3 months ago
<input type=“text” defaultValue={user.email ?? “”}>
The user is an entity from the DB, where the email field is nullable, which makes perfect sense. The input component only accepts a string for the defaultValue prop. So you coalesce the possible null to a string.
unknown|3 months ago
[deleted]
unknown|3 months ago
[deleted]
clnhlzmn|3 months ago
Isn't this more like `unwrap_or`?
fred_|3 months ago
aeonfox|3 months ago
vadansky|3 months ago
```ts user?.name ?? "" ```
The issue isn't the nullish coalescing, but trying to treat a `string | null` as a string and just giving it a non-sense value you hope you'll never use.
You could have the same issue with `if` or `user?.name!`.
Basically seems the issue is the `""`. Maybe it can be `null` and it should be `NA`, or maybe it shouldn't be `null` and should have been handled upstream.
btdmaster|3 months ago
[0] https://news.ycombinator.com/item?id=41031585
yieldcrv|3 months ago
Is this a kink? You like being on call?
You think your performance review is going to be better because you fixed a fire over a holiday weekend or will it be worse when the post mortem said it crashed because you didn’t handle the event bus failing to return firstName