I wanted to implement a notification message in one of my projects, similar to what you’d see in Google Docs while a document is saving. In other words, a message shows up indicating that the document is saving every time a change is made. Then, once the changes are saved, the message becomes: “All changes saved in Drive.”
Let’s take a look at how we might do that using a boolean value, but actually covering three possible states. This definitely isn’t the only way to do this, and frankly, I’m not even sure if it’s the best way. Either way, it worked for me.
Here’s an example of that “Saving…” state:

…and the “Saved” state:

Using a Boolean
value for to define the state was my immediate reaction. I could have a variable called isSaving
and use it to render a conditional string in my template, like so:
let isSaving;
…and in the template:
<span>{{ isSaving ? ‘Saving...’ : ‘All changes saved’ }}</span>
Now, whenever we start saving, we set the value to true
and then set it to false
whenever no save is in progress. Simple, right?
There is a problem here, though, and it’s a bit of a UX issue. The default message is rendered as “All changes saved.” When the user initially lands on the page, there is no saving taking place and we get the “Saved” message even though no save ever happened. I would prefer showing nothing until the first change triggers the first “Saving” message.
This calls for a third state in our variable: isSaving
. Now the question becomes: do we change the value to a string variable as one of the three states? We could do that, but what if we could get the third state in our current boolean variable itself?
isSaving
can take two values: true
or false
. But what is the value directly after we have declared it in the statement: let isSaving;
? It’s undefined
because the value of any variable is undefined
when it’s declared, unless something is assigned to it. Great! We can use that initial undefined
value to our advantage… but, this will require a slight change in how we write our condition in the template.
The ternary operator we are using evaluates to the second expression for anything that can’t be converted to true
. The values undefined
and false
both are not true
and, hence, resolve as false
for the ternary operator. Even an if/else statement would work a similar way because else
is evaluated for anything that isn’t true
. But we want to differentiate between undefined
and false
. This is fixable by explicitly checking for false
value, too, like so:
<span>
{{ isSaving === true ?
‘Saving...’ :
(isSaving === false ? ‘All changes saved’: ‘’)
}}
</span>
We are now strictly checking for true
and false
values. This made our ternary operator a little nested and difficult to read. If our template supports if/else statements, then we can refactor the template like this:
<span>
{% if isSaving === true %}
Saving...
{% elseif isSaving === false %}
All changes saved
{% endif %}
</span>
Aha! Nothing renders when the variable is neither true
nor false
— exactly what we want!
This means you could also use
null
,0
,""
,'asdf'
or anything buttrue
andfalse
to indicate states. In some cases, I would prefer numeric values stored in named constant to indicate custom states for readability.I think it would be better to just use 3 statuses and store them either as strings or as integers.
1 – LOADED
2 – UNSAVED
3 – SAVED
For me, when dealing with asynchronous interactions like this, I would use a state variable called saveState which can be any of the following values: ‘IDLE’, ‘LOADING’, ‘SUCCESS’, ‘FAIL’. That way you could handle failed requests
I agree with this than the article. undefined should be used with undefined semantics. If it is used for other purposes, it will be confusing.
Yeahs ~ sometimes it works well , and I’d like to compare ‘undefined’ to a ‘missing state’ , no ‘isSaving’ and no descriptions of “Saving…” state
Relying on an uninitialized variable is not something I would do;
It makes it harder to understand the code by looking at it.
You have to consider that variable any time you change the code around it, increasing the friction in the system.
Type analysis and checking can get confused.
Related to the previous point; a small bug in a runtime/compiler in regards to hoisting will give a different behaviour.
But above all, it is an indication that you may be missing something that would decrease coupling and increase testability. I would have expected to find some sort of queue of changes to save, or a repository, that I could query for outstanding operations, for example.
I suggest the three boolean states should be renamed True, False, and Cat. I am sure such naming would soon become popularly used.
A bit easier to read when you get used to it:
{{
isSaving === true
? ‘Saving…’
isSaving === false
? ‘All changes saved’
: ‘’
}}
What a bad example is given here. There are abstractions which models the presence or absence of values. Some JS implementations: https://gcanti.github.io/fp-ts/modules/Option.ts.html https://folktale.origamitower.com/api/v2.3.0/en/folktale.maybe.html https://funfix.org/api/core/classes/option.html
This is pretty bad, if I were to find this code in a project I was working on, I’d refactor it immediately (after trying to understand what the hell is happening). First of all, boolean variables shouldn’t be used like this, it’s confusing to other people reading the code and is very literally a hack. Second of all, nesting ternary operators is also pretty messy code, imo
Ternaries don’t have to be ugly. It’s just a question of formatting. For instance in JSX :
Note that PHP is not so good with ternary chaining.
This implementation has its limitation. What is the save failed and I would like to update the status message to convey it.
I would prefer to use more defined state constants here.
Yeah, I agree with the other posts that this is an anti-pattern. Trying to give
undefined
extra meaning creates room for subtle bugs to creep in. It creates implicit knowledge in your code thatundefined
actually means “initial state”, as opposed to, “i forgot to set this variable”. Having a string makes the variable a consistent type, which makes type checking easier, and it makes the value of the variable more immediately clear as to what it represents, especially in cases where it gets passed to other functions or is stored separate from where it is used. I’d probably extract the strings into a constant:This way you avoid the conditional logic entirely, and can trivially add new states, such as
error
.