I would define a framework as something that offers a high level abstraction, promotes best practices and takes care of a bunch of boilerplate stuff for you.
React doesn't do that at all. Next, Blitz, Redwood etc, do.
- It "calls you" (i.e. you never explicitly call your React components, React does)
- It permeates your codebase (e.g. migration from moment to date-fns is something you can realistically do piecemeal, React to Vue or vice versa not so much)
- It enforces a specific architectural paradigm (components) to the exclusion of others (e.g. MVC, MVVM, etc)
- Its direct competitors are frameworks (Angular, Vue, Backbone, Aurelia, Mithril...)
- it is composed of multiple libraries (react, react-dom)
- Experience in it is considered a primary hiring criteria in many shops (which typically isn't the case for libraries)
Almost everything in your first few points there depends on how you choose to use React, though, rather than being inherent in the library itself.
It "calls you" (i.e. you never explicitly call your React components, React does)
The starting point for doing anything with React is where you call it to render a certain component at a certain point in your document. There are things built on top of React, such as Next.js, that add more layers and do follow more of a framework design, but even there they are still calling React when they need to rather than the other way around.
It’s true that React provides features can automatically trigger a rerender later on. You don’t have to use any of those, though.
There are also integrations with many other libraries, such as those for managing state in one way or another, where the library can trigger rerendering of specific React components when relevant state changes. In this case, it’s the library calling React rather than React calling out.
Another alternative is the original React “philosophy”, simply rerendering the entire tree whenever you need to and letting the VDOM mechanism and perhaps a few helping hands that avoid unnecessary rerenders in your components keep this reasonably efficient. When to call into React for a rerender is entirely your own choice.
I suppose you could argue that React operates somewhat like a framework because it calls the render function for each component, but to me this is more analogous to a generic sorting algorithm that calls a comparison function I pass in when it needs to. It still doesn’t do anything until I call `sort`, and the code I write is still what decides the overall control flow for the program.
It permeates your codebase (e.g. migration from moment to date-fns is something you can realistically do piecemeal, React to Vue or vice versa not so much)
It is possible to use React only as a convenient rendering library and keep other responsibilities like state management and communications separate, just like any other software.
Some people choose not to do that and instead write React components that manage state and call remote APIs and cook you breakfast in the morning. Whether this strategy is wise is a different question.
I’m working on a new front-end using React right now, and even at an early stage when presentation code is a large part of what has been written so far, maybe 1/3 of the code even knows React exists. I’ll be surprised if that figure is even 20% by the time we launch. Even now, we could drop React tomorrow in favour of any other rendering code, and nothing would need to change in the code that handles our data model, diagram layouts, communications with our back-end APIs, etc.
It enforces a specific architectural paradigm (components) to the exclusion of others (e.g. MVC, MVVM, etc)
Components are just a way to build a rendering hierarchy: data in, DOM out. Nothing about that forces you to adopt any specific architecture in the rest of your application. Again, some people do choose to put other responsibilities within their components, even using React components to model non-presentational aspects so almost their entire front-end architecture becomes one big tree of React components, but you don’t have to do this and then deal with all the problems it potentially creates that you didn’t otherwise have.
> The starting point for doing anything with React is where you call it to render a certain component at a certain point in your document
You're trying to use the literal meaning of "you call it" to make an argument but that argument doesn't support the position you think it does. Yes, React has `ReactDOM.render()`, but did you know Angular.js also has an entry point called `angular.bootstrap()`? Vue has `new Vue()` and Mithril has `m.mount()`. In all of these frameworks there are various callable APIs, but that fact doesn't oppose the fact that your React component code is called by React, not by you.
> It is possible to use React only as a convenient rendering library
This is also possible with Vue, Hyperapp, Choo, Mithril and many other frameworks.
> Nothing about that forces you to adopt any specific architecture in the rest of your application
Again, same can be said about various frameworks. You don't need to use Vuex. Lichess uses MVVM w/ Mithril. Etc. Ultimately, componentization offers a prescriptive organization pattern. Nobody complains about React code being spaghetti like they do about jQuery, and for a good reason (sagas and other auxiliary libraries aside)
I would say the traditional distinction between a library and a framework, to the extent that the industry has any settled definition for these terms at all, is about who dictates the application architecture and the control flow.
A framework typically establishes a runtime environment and then calls into your code to respond to specific events. Common examples are GUI applications that run an event loop responding to operating system events and web servers where the entry points to your own code are handlers for different requests. In the early days, JavaScript was mostly used similarly, as the entry point to your own JS code tended to be a specific event on a specific DOM node. There might be some code to set things up when your program starts, but once you hand over control to the framework, it decides what the run and when, and your code doesn’t do anything unless the framework asks it to.
A library, in the not-a-framework sense, works the other way around. Your own code ultimately has control and you call into the library when you require specific functionality. The library doesn’t do anything until you ask it to.
So to answer your question, yes, I did know those things, but I don’t think the equivalence between those calls is necessarily appropriate in this context. It’s not just about who calls the function the first time. It’s also about what happens next — whether your own code continues to call the library in a similar way later, or whether you hand over control to a framework once and that deals with everything from then on. Calling ReactDOM.render doesn’t have to be a fire-and-forget operation; it can simply perform a single render and then return having completed its task. You might then call it many further times to rerender that part of your page in response to changes in the data it depends on.
Control will often flow back and forth between your own code and library or framework code. Sometimes this happens on a small scale, like the sorting example I mentioned where you are calling a higher order function and it takes a simple callback function as a parameter. Sometimes it happens on a much larger scale, like calling React to render a component you supply, and then React calling that component’s own render logic, which in turn calls back into React to create child components, and then React calling their own render logic, and so on.
You can certainly argue that the latter starts to resemble a framework, but if you’re only talking about rendering a component tree that is triggered by a call from your own code and returns control to your code again once the rendering has finished, the difference is merely a matter of scale. Surely everyone would agree that the sorting example is a library call, so where do you draw the line? The overall architecture of your application and its control flow is still determined by your own code.
You certainly can use React more like a framework, in the sense that you call into it once to set things up and then hand over control. You can write components that have additional responsibilities like maintaining your application state and fetching data from remote APIs, and you can use hooks (or class-based component state and lifecycle methods) so that interesting events from those other sources will automatically trigger a rerender.
My point is that you don’t necessarily have to design your application in that style just because you’re using React. If you choose not to, then React doesn’t “call you”, it doesn’t permeate your codebase in the way you described before, and it certainly doesn’t exclude the use of architectural patterns like MVC or MVVM.
I don't think it makes sense to say "React is a library because you can forego using enough of its features to fall below some arbitrary line". By that logic, I can include Ember in a page and not use any of its features other than `console.log(Ember.VERSION)`, and voila, "it doesn't call you", "it doesn't permeate the codebase", "it doesn't exclude any architectural patterns", therefore it's not a framework. This line of reasoning clearly culminates in reductio ad absurdum.
IMHO, it's more fruitful to ponder the possibility that certain projects are not frameworks because they fundamentally lack something that frameworks provide. Suppose we tried to use nothing but lodash to implement a web app. It's certainly doable (given that people write vanilla apps), lodash would "permeate" the codebase and it would probably call a sizable number of your callbacks. But I think we'd both agree lodash is not a framework. But why? I'd argue it's because organizing units (in the "unit test" sense) coherently in a way that is maintainable is outside of its expressed scope. In other words, it's not suitable to that task, even if we wanted it to. React is not only suitable for it, it's specifically designed for that purpose.
Speaking of units, another way we can approach this is by looking at where unit boundaries are in comparison to user code boundaries. If you look at any usage of node's `child_process` API, the event handler functions are generally not considered units; the entire child_process instance setup might be one unit, and the event handlers may call other units that are ideally decoupled from whether callback arguments are partial Buffers or error codes and so on. Similarly, a sorting callback is not typically a unit (i.e. you never test that the callback returns 1 or -1, you test that the code sorts correctly instead). React components on the other hand are units in and of themselves. I believe it is accurate to say that frameworks provide abstractions whose purpose is to delineate where a unit boundary is. Components fit that bill perfectly, just as controllers and views and services do in other architectures.
I think you’re being too pedantic. In 99% of React apps, React “establishes a runtime environment and then calls into your code to respond to specific events”, as you put it.
You’re still writing code which is based on React’s view of the world, not standard web APIs. Saying it’s not a framework just means that people are cobbling together their own frameworks using some popular libraries, which gives more flexibility at the cost of the various combinations being less tested and harder for someone else to support.
To be clear, this can be a reasonable trade off – but there’s a cost which people often discount until they realize they just spent a week wrestling with abstraction layers.
React forces you to write code a certain way. jQuery does not force you to write your code a certain way. In my experience, that’s the single biggest difference between a library and a framework.
React doesn't do that at all. Next, Blitz, Redwood etc, do.