Nikita Lozgachev is a software engineer and functional programming advocate, specializing in reducing complexity, crafting clean abstractions, and driving framework-agnostic architecture.

Email LinkedIn GitHub RSS

The State Within

In the dim glow of a computer screen, I often find myself asking questions that linger long after the work is done. Why, when the tools of our craft could be as universal as the languages they extend, do front-end software engineers chain themselves to frameworks and abstractions that fade with time? Why, when the separation of state and representation opens the door to testability and freedom, do developers bind them together so tightly that a single change threatens the entire structure? And why is the simpler way — the way closer to the languages we write in — spoken of so rarely, written about in passing, almost as if it were a secret?


A tool is not a solution. It is a lens, a way of framing the problem — but the problem itself remains. Too often, we mistake the tool for the answer, letting its features dictate our design, its limitations define our scope. But a tool should not lead; it should follow. It should adapt to the problem at hand, bending to fit its contours, not the other way around. When we let a tool take center stage, we risk building systems that serve the tool instead of the developer, architectures optimized for the mechanics of the framework rather than the needs of the application. And so the tool, which should have been a servant, becomes a master. To truly solve a problem, we must look past the tool, to the foundations — the data, the flow, the purpose. Only then can the tool take its rightful place, not as the star, but as the stagehand, quietly enabling the real work to shine.

React Context introduces itself with the confidence of a tool designed to bring structure where there was once only chaos. It presents a vision of state shared effortlessly, cascading down the component tree like water finding its way through branches to leaves. For a moment, you believe it. The API feels intuitive, its purpose clear. But as you begin to work with it, you sense an undercurrent. Context, you realize, is not a tool of simplicity — it is a tool of order.

Sometimes, we build structure where none is needed. A ladder raised to reach what is already within grasp. Context, too, can become this ladder. It begins with a single shared state — a practical choice, a solution to a clear problem. But as layers accumulate, the architecture sways under its own weight. Each addition promises clarity but obscures instead. Engineers do not question why they build; engineers build because they can.

The same can be said of inheritance, a once-venerated pattern. It tempts us with hierarchy, with relationships neatly defined. But each level adds fragility. Child depends on parent, parent on its parent. What was once clear becomes brittle. When a simple composition of behaviors would suffice, we instead erect monuments to the complexity we create. Entire bookshelves have been dedicated to the art of managing the consequences of object-oriented inheritance. They teach you how to navigate the problems, but rarely question why the problems exist at all. They focus on refining the structure, rather than stepping back to ask if the structure is even necessary. Developers are taught to deal with consequences of inheritance “the right way” rather than acknowledging that perhaps the best way is to avoid it altogether.

Hooks are another kind of temptation. They whisper of immediacy and power. Of state and side effects seamlessly integrated. But they, too, carry hidden costs. With every dependency, every effect, every link to the representation layer we tighten the threads of our application. Forget one, and the system frays. Add too many, and it tangles.

You think you’re in control, but are you? Each useEffect pulls at the structure, weaving itself into the logic of your system. Each forgotten dependency threatens its integrity. Like a web spun in haste, it may hold for now — but strain it, and it will fail.

State is not a property of components or hooks. It is not inherently tied to a rendering cycle or a virtual DOM. State is a snapshot of the system at a moment in time, a collection of truths that can stand apart from how those truths are displayed. The simplest state management libraries remind us of this. They allow state to exist as its own entity, free to be passed, shared, or modified without being tethered to the mechanics of the UI.

But there is another way. State can flow directly to the components that need it, unencumbered by the intermediaries. Libraries like MobX, Zustand, Jotai, Valtio, Recoil, nanostores, and even the venerable Redux offer approaches that decouple logic from UI, letting state exist as a self-contained system. Observables let components subscribe to the pieces of state they care about, updating themselves only when necessary. No more drilling props. No more wrapping components in layers of context or hooks. The state flows where it is needed and nowhere else.

Observable state, immutable stores, reactive patterns — these are not new ideas, but they are enduring ones. They ask us to model state as something universal, independent of any framework. With these tools, a component becomes what it was always meant to be: a view, a projection of state onto a user’s screen. The logic that drives it exists elsewhere, untouched by the cycles of rendering or the constraints of frameworks.

Logic that lives outside the framework is logic that can be tested on its own terms. A reducer, an observable, a function — these can be tested in isolation, without the need for rendering or mocking a virtual DOM. Unit tests become faster, simpler, and more reliable. You do not need a headless browser to confirm that a function was run with specific arguments or that some data changed correctly. You do not need to simulate clicks or keypresses when you can invoke the logic directly. A test for an action or an observable state can execute in milliseconds, running directly in the environment of the language. This speed changes how you approach testing. You write more tests because the friction is lower. You iterate faster because the feedback is immediate. And you gain confidence in your system.

Logic written outside a framework is logic that transcends it. A reducer is not tied to React. An observable is not bound to Vue or Svelte. These constructs are portable. They can be reused across projects, shared between teams, or migrated to new frameworks as the landscape evolves.

When you write in this way, you are not building for a framework — you are building for the system. You are creating something that can endure, even as the tools around it change.

This is not a rejection of tools carried by frontend frameworks. They have their place. But we must use them wisely, sparingly, and with the understanding that they are not the solution — they are the means. They are the bridge, not the destination.

When solving a problem, ask yourself: Can this be done closer to the language? If the answer is yes, do it. Avoid the temptation to reach for the most elegant-looking library or the most popular abstraction within the framework. Elegance is not found in cleverness but in clarity.

Frameworks are scaffolds, a facade that connects us to browser API, built to help us reach higher, faster. But scaffolds are not the building. When the scaffold is removed — or replaced — what remains? Will the system stand, or will it collapse under the weight of abstractions no one remembers how to maintain?

But the foundations of a language are timeless. They are the shared knowledge of the craft. When we write closer to the source, we write in a language that endures, that can be read and understood by those who know the craft, regardless of the tools they prefer.

As we build, we must ask: Are we creating something that will stand the test of time? Something that will bring joy to those who inherit it? Or are we binding ourselves and others to fragile, overburdened systems?

The tools we use should serve the system, not define it. They should not constrain — they should liberate. They allow systems to grow and adapt without unnecessary weight. They speak of simplicity, clarity, and life. And if we listen closely, they might tell us something more.

Acknowledgments

  • Brian Will. (2009-Present). Video-essays.
  • Paul Graham. (2003). The Hundred-Year Language.
  • Kent Beck, Martin Fowler. (1999). Refactoring.
  • Eric Steven Raymond. (2003). The Art of Unix Programming.
  • Donald Knuth. (1968–). The Art of Computer Programming.
  • Gerald Jay Sussman, Hal Abelson, Julie Sussman. (1985). Structure and Interpretation of Computer Programs.