What is state — and why a button doesn't "turn" grey

There's an idea hiding here that makes interfaces stop feeling like magic.
You hit "Submit." The button turns grey and a spinner starts. Seems obvious: the code grabbed the button and painted it.
Except nobody touched the button. One line somewhere nearby changed — "submitting: yes." And the button got drawn again, grey this time, because it's always drawn from that line.
That line is state. And nearly all interface pain is about it.
What state is, in plain words
State is everything your app remembers right now.
Simple test: if you closed your eyes and opened them again, what would the app need to know to draw the same screen? That's state.
On a to-do screen, for example:
- the list of tasks that loaded from the server;
- which ones are checked;
- whether something is loading right now;
- the text you typed into the search box;
- whether you're logged in.
Notice what didn't make the list: "blue button," "16px font." That isn't state, that's styling — it never changes. State is only what changes and needs to be remembered.
Two useful piles:
- Data state — what came from the server. The list, the profile, the balance.
- UI state — what's happening on screen. Is the menu open, which tab is active, what's in the input.
The first usually lives elsewhere and travels to you. The second is born and dies with the screen.
Why you change the state, not the button
Before modern UI libraries, people wrote it exactly the intuitive way: find the button in the DOM, disable it, change its text, then remember to enable it again.
The problem showed up at the third condition. The button is disabled while submitting. Also when the form is empty. Also when there's no internet. Now every change to any of the three means manually recalling all the others. Miss one combination and the button is grey forever, and nobody knows why.
So the approach got flipped. You don't describe transitions ("this happened — now do that"). You describe the picture: what the screen looks like given this state. And the library — React, for instance — figures out what changed and redraws what's needed.
The interface becomes a function of state. Same state, same picture, every time.
Which gives you the right question when something breaks. Not "why is the button grey," but "what state makes it grey, and who set it that way?"
Where interfaces break: two truths instead of one
Now the main thing. The most common and most miserable class of UI bug is one truth stored in two places.
It looks harmless. You have a "notifications on" toggle. You keep it in screen state so it flips instantly. It also exists in the profile that came from the server.
Two copies of one fact. From there it's a matter of time:
- The server request failed, but the toggle on screen already flipped. The user sees "on," the server thinks "off."
- The user left for another screen and came back. One screen re-read the profile, the other shows the stale copy.
- You open the app on your phone and your laptop. They disagree.
And all the code is correct. There are just two truths now, and they inevitably drifted apart.
A common variant of the same thing: state you could have calculated. You store the task list and, separately, a number for "how many are left." One updates, the other gets forgotten. The readings diverge.
Models love this, by the way: ask an agent to add a counter and there's a good chance it declares a fresh variable next door, because that's shorter. This is a case where the diff deserves a careful look.
One source of truth
The rule that closes almost all of the above: every fact has exactly one home address.
Three practical consequences:
- Don't copy — point. If the toggle already lives in the profile, the screen should read it from the profile instead of keeping its own copy.
- Don't store what you can compute. "How many are left" isn't state, it's arithmetic on the list. Compute it while drawing. Then nothing can drift.
- Lift state to where everyone who needs it can see it. If two screens change the toggle, it belongs to neither. Its home is higher up.
And one debugging move that saves hours. When the UI acts strange, don't dive into the rendering code. Dump the entire screen state — one object, straight to the console. Nine times out of ten you'll see it immediately: either the field isn't what you thought, or there are two of them. From there you can hand that object to a model — with the full state in hand it guesses incomparably better than from "the button doesn't work."
How is state different from a variable?
All state lives in variables, but not every variable is state. State is what the picture on screen depends on: it changes, something must be redrawn. A temporary variable inside a function has no such property.
Where should state actually live?
Start as narrow as possible: inside the screen that needs it. Lift it only once a second screen asks for it. Reaching for a global store on day one is a reliable way to complicate a project too early.
Why does my app forget everything on reload?
Because state lives in memory, and memory gets cleared. To survive a reload it has to be written somewhere — browser storage, a file, or the server. That's a separate decision, and worth making deliberately: not everything deserves to be remembered forever.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.





