Designing state
Proper state management is crucial to keep a lid on complexity, especially in UI frameworks such as React. There are a number of patterns you can use to keep a handle on things.
Grow state linearly (avoid boolean flags)
There’s a temptation, when a new edge case or requirement meets a software component, to represent the change by adding a boolean flag to the state space of the component to accommodate the new condition:
// Eight possible states
type Props = {
isFoo: boolean, // 2
isBar: boolean, // x 2
isBaz: boolean, // x 2
};
The problem with this, as you see above, is that it makes your state space grow quadratically. Each new boolean option multiplies the number of potential states by 2. Do this a couple of times, and your component quickly balloons to a number of possible states you can neither test nor reason about. You’re all but guaranteed to have a combination somewhere in there that at best has not been tested, inviting bugs; or at worst, isn’t even logical or self-consistent.
Instead of adding boolean options, try to plan the possible states your component can be in, and express those explicitly as an enumeration (structural type systems and those with algebraic data types are great at this):
// Three possible states
type Props = {
mode: 'foo' | 'bar' | 'baz', // 3
};
This has a couple of nice effects. First, it makes you think about every state ahead of time, giving you a much more intentional grasp of your component and how it should work. Second, it makes it so the number of states grows much more slowly when adding new states down the road. If you can absorb a new state in one of these enumerations, you’re only taking your state space from m * n states to m * (n+1), instead of 2 * m * n.
// Four states
type Props = {
isFoo: boolean, // 2
mode: 'bar' | 'baz', // x 2
};
// Six states
type Props = {
isFoo: boolean, // 2
mode: 'bar' | 'baz' | 'qux', // x 3
};
// Eight states
type Props = {
isFoo: boolean, // 2
mode: 'bar' | 'baz' | 'qux' | 'borf', // x 4
};
As a bonus, not only does this prevent a state space explosion, but it helps you keep invalid states unrepresentable.