> It's a useful model, but I've found that it can break down a bit on really large, complex apps. If a piece of state is being passed to multiple places via props (either manually or using something like Redux), it can be difficult to see at a glance every place in an app that a single bit of state is being used.

This is why a UI app state query-based approaches (ex: om next) on a single app state tend to be a bit easier, but still can suffer from what you describe (especially if queries are dynamically built by a user or something). At a minimum, you can either re-use the same query or at least know through the query syntax where your code is touch some bit of state as it should be available for analysis by an IDE and easily searchable as plain text. Still, there's not really a perfect solution I've seen and eventually things can get messy if you aren't careful and as you increase the number of developers touching code.

In general, updating a UI only based on changes is a great approach. The challenge has always been identifying what changed and minimizing the tracking of those changes in scalable way. I saw many old approaches use things like dirty flags everywhere or doing field comparisons one by one. Things like react frameworks in Clojurescript make this so much easier today because you can do a very fast identity check that is cheaper using immutable structures like those in Clojure. If your check itself is expensive, the benefits of a delta-based approach are limited vs. giving up and doing full re-renders. I've hit this in game programming either way, but usually the change approach wins unless there's some very specialized case or design issue or simply just tons of raw power it's not an issue anyway.

Where events themselves suck is predictability. This is doubly so for systems that introduce event hierarchies i.e. inheritance-like constructs for events. I strongly prefer deterministic approaches to rendering when possible. That's not to say you re-render at a fixed interval, but rather attempt to re-render changes only if they exist. It makes debugging, optimizing, and understanding the system so much easier.

From a performance and debugging point of view, events or signals and slots tend to cause situations where you're jumping so much over the code you lose all kinds of CPU cache or GPU benefits (buffers, batching, pipelining, etc.) depending on what you are doing. Also some event systems use a lot of objects and in systems requiring allocations on the heap and/or garbage collection, this can become really ugly if the app is running for awhile. Events do make things easy for small projects, but tend to create spaghetti for larger ones, even with a single event bus. Approaches using loops, deltas and possibly queues/mailboxes tend to scale a lot better for games, and also for apps that have performance issues. A side benefit is your state tends to become a functional reduction, which has its own benefits such as making undo/redo, logging, and error handling easier.

Sometimes I'm rather confused by all the UI issues created in app dev. I understand them, but as someone who has many decades of experience doing both game and app development, I'm like wtf app programmers. React and things like it are or can be at least a bit closer to loop, pipelined, time approach that's used in modern game architectures precisely to achieve consistent performance and do sane things in the face of GPUs.