I spent most of 2014 writing Angular. I liked it well enough. The two-way data binding felt magical when it worked. Then React came along and told me I had been thinking about UI all wrong.
React's central idea sounds almost too simple to be revolutionary. UI is a function of state. Given the same state, you always get the same UI. When state changes, React figures out the minimum set of DOM changes needed to update the UI to match the new state.
That is it. That is the core idea. But unpacking what it means in practice took me a while.
The thing that clicked first was the mental model. With two-way data binding, changes could flow in any direction. State could update the UI, and the UI could update the state. This made the system hard to reason about. Something changed somewhere and you had to trace through a web of bindings to understand why.
React's one-way data flow meant changes only went one direction. State flows down through components as props. Events flow back up through callbacks. You always knew where state lived and how changes propagated. The system was easier to reason about because there was only one way it could work.
Components were the second big idea. React made components the fundamental building block of UI. Not pages, not routes, not controllers. Components. Small, self-contained pieces of UI that composed together. This was not entirely new, Web Components had been discussed for years. But React made it practical and the component model quickly became the dominant way to think about frontend architecture.
The virtual DOM confused some people initially. React creates a JavaScript representation of the DOM and diffs it against the real DOM to figure out what actually needs updating. People worried this was slow. In practice, for most applications, it was fast enough, and the programming model benefits were worth any performance overhead.
JSX was controversial. Writing HTML inside JavaScript felt wrong to people who had spent years separating concerns between HTML, CSS, and JavaScript. But JSX turned out to be a practical choice. Putting the template and the logic in the same file meant related code stayed together. When you changed a component's behaviour, you changed one file, not three.
By the end of 2015, React had accumulated enough momentum that it was hard to ignore. The community was producing tools, patterns, and libraries at a pace that dwarfed other frameworks. The patterns for state management, routing, and testing were settling. Teams that adopted React early found themselves with a frontend architecture that was easier to maintain and easier to hire for than the alternatives. That combination proved hard to beat.