Type-safe string state instead of booleans
When it comes to controlling frontend presentation, developers often rely on booleans. However, this approach has serious flaws.
Here’s something a bit better: type-safe strings. Frontend UI state often starts as a boolean (React example):
import {useState} from 'react';
const [modalVisible, setModalVisible] = useState(false);When modalVisible is true, the modal is visible. We can then show or hide
the modal with its setter:
const showModal = () => { setModalVisible(true);
// ...user interacts with modal...
setModalVisible(false);};
return modalVisible ? <Modal /> : null;It’s time for an additional feature request! Our stakeholder now wants three modals, one to create a thing, one to update that thing, and one to delete that thing.
What should we do? The conventional answer is to add more state.
const [createModalVisible, setCreateModalVisible] = useState(false);const [updateModalVisible, setUpdateModalVisible] = useState(false);const [deleteModalVisible, setDeleteModalVisible] = useState(false);Additional feature request incoming! We’d like to creating, updating, and deleting three different kinds of things on our page.
const [createMeetupModalVisible, setCreateMeetupModalVisible] = useState(false);const [updateMeetupModalVisible, setUpdateMeetupModalVisible] = useState(false);const [deleteMeetupModalVisible, setDeleteMeetupModalVisible] = useState(false);
const [createOrganizerModalVisible, setCreateOrganizerModalVisible] = useState(false);const [updateOrganizerModalVisible, setUpdateOrganizerModalVisible] = useState(false);const [deleteOrganizerModalVisible, setDeleteOrganizerModalVisible] = useState(false);
const [createTopicModalVisible, setCreateTopicModalVisible] = useState(false);const [updateTopicModalVisible, setUpdateTopicModalVisible] = useState(false);const [deleteTopicModalVisible, setDeleteTopicModalVisible] = useState(false);We’re starting to hate this code. Why? I think it’s a pretty fundamental problem: the use of booleans.
Booleans are a blunt instrument. They have just two states. But many stateful UI interactions need more than two states.
Consider our example above. What are all these state hooks trying to answer? It’s not: “Is this modal visible, and this one, or this one, and this one…?” Rather, it’s “Which modal is visible?”
These modals are exclusive— only one should be visible at a time. And so, this
isn’t a case of true or false, it’s a case of which. A boolean is the
wrong tool for this problem. There’s a great post linked at the bottom by Matt
Pocock that expands on this argument.
So, what’s the solution? Here’s a proposal:
const [modalVisible, setModalVisible] = useState();To show a modal for creating a Meetup event, we set a string value in state.
const showNewMeetupModal = () => { setModalVisible('new-meetup');
// ...user interacts with modal...
setModalVisible(undefined);};
const newMeetupModalVisible = modalVisible === 'new-meetup';
return newMeetupModalVisible ? <NewMeetupModal /> : null;With this implementation, modalVisible can hold infinite possible single
modals, or no modal, with a string or undefined (or an empty string if you
prefer). The return logic can be abstracted to function containing a tidy
switch statement.
But wait; isn’t relying on perfect strings brittle? One extra dash in
"new-meetup" becomes "new--meetup", and no modal appears. Enter type safety!
We can type the modal with a TypeScript union, limiting what is allowed.
type Modal = 'new-meetup' | 'edit-meetup';
const [modalVisible, setModalVisible] = useState<Modal | undefined>();
setModal('new-meetup'); // Ok ✅setModal('new--meetup'); // Type error ❌- Union Types — TypeScript Handbook
- Literal Types — TypeScript Handbook
- State Management: How to tell a bad boolean from a good boolean — Matt Pocock