Introduction
We often use boolean states to keep track of whether something is loading.
Usually, it works as expected.
But maybe you should stop doing it. It's almost always better to have a status.
Let's dive into an example to clarify the dangers of using boolean states. I will stick to plain JavaScript for demonstration purposes, but it applies to any frameworks you'd be using.
The problematic approach
To make things a bit clear, I'm taking the extreme approach. Let's look at a piece of code for our registration process and examine how boolean states could become problematic:
let isValidating = false;
let isCreatingAccount = false;
let isSendingEmail = false;
let isRegistrationComplete = false;
let hasValidationError = false;
let hasCreationError = false;
let hasEmailError = false;
function registerUser(userData) {
isValidating = true;
// Step 1: Validate user data
validate(userData, (validationResult) => {
isValidating = false;
if (!validationResult.ok) {
hasValidationError = true;
return;
}
// Step 2: Create account
isCreatingAccount = true;
createAccount(userData, (creationResult) => {
isCreatingAccount = false;
if (!creationResult.ok) {
hasCreationError = true;
return;
}
// Step 3: Send welcome email
isSendingEmail = true;
sendWelcomeEmail(userData, (emailResult) => {
isSendingEmail = false;
if (!emailResult.ok) {
hasEmailError = true;
return;
}
// Step 4: Registration complete
isRegistrationComplete = true;
});
});
});
}
How can this become problematic?
Complex conditional logic: At each step, you need to check multiple flags to understand the current state.
Hard to maintain: If a new step or error condition is added to the process, you need to add more flags and update the logic in several places.
Risk of contradictory states: There's a risk that multiple flags could be
true
at the same time due to bugs, leading to states that don't make sense.
The right approach
Let's take a look at how this would be different when using a status.
let registrationStatus = 'idle'; // Can be 'idle', 'validating', 'creatingAccount', 'sendingEmail', 'complete', 'error'
function registerUser(userData) {
updateRegistrationStatus('validating');
// Step 1: Validate user data
validate(userData, (validationResult) => {
if (!validationResult.ok) {
updateRegistrationStatus('error', validationResult.error);
return;
}
updateRegistrationStatus('creatingAccount');
// Step 2: Create account
createAccount(userData, (creationResult) => {
if (!creationResult.ok) {
updateRegistrationStatus('error', creationResult.error);
return;
}
updateRegistrationStatus('sendingEmail');
// Step 3: Send welcome email
sendWelcomeEmail(userData, (emailResult) => {
if (!emailResult.ok) {
updateRegistrationStatus('error', emailResult.error);
return;
}
// Step 4: Registration complete
updateRegistrationStatus('complete');
});
});
});
}
function updateRegistrationStatus(newStatus, error) {
registrationStatus = newStatus;
if (error) {
console.error(`Registration error: ${error}`);
}
console.log(`Registration status: ${registrationStatus}`);
}
This is so much easier to maintain, change and use.
We can only be in one state at a time.
You might wonder what could happen if someone mistypes a status?
Then use a typed language!
If that isn't possible, use a constant to be consistent with the naming of the different statuses.
Conclusion
Stick to using statuses, not boolean states.