- 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 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".
Please note that I’m not arguing that React itself is or isn’t a framework. I’m simply saying that it can be used in multiple and qualitatively different ways, in contrast with your own position in the comment I originally replied to.
In particular, nothing stops you from using React only for what it’s really good at: providing a declarative way to specify the required rendering behaviour, with reasonable modularity due to the component system and reasonable efficiency for many practical applications due to the VDOM. This was the combination of ideas that React popularised within the front-end web development community, the innovation that moved the goalposts in that part of the software industry. You can also use other libraries for what they’re good at, whether that is state management or accessing remote APIs or doing a fuzzy string look-up to power a search box. And you can use your own code to implement your application-specific functionality, as well as delegating to and co-ordinating between these other dependencies whenever it makes sense.
None of this is a novel or React-specific idea. We’ve been building software effectively with modular architectures and separation of concerns for many decades. Isolating presentation code from other aspects is perhaps the most common example, and patterns formalising different arrangements of those responsibilities have been around since at least the 1970s.
I appreciate that this is not necessarily the most trendy or popular way to use React today. There is no shortage of libraries and blog posts and forum comments that actively encourage building your whole application as one big tree of React components, even if those components have little if anything to do with presentation and rendering. But in a discussion about whether modern practices are really a good idea, I think it’s reasonable to point out that there is also no shortage of blog posts and forum comments and Stack Overflow questions asking how to solve problems with that kind of application that wouldn’t have existed in the first place with a better architecture.
It’s very late here so this will be my last comment for tonight, but just to finish, your last paragraph provides some great examples of how different the two approaches we’ve been discussing can be. I absolutely do have unit tests for custom comparison functions in the project I’m working on right now, because there is significant decision-making logic inside them, and if that logic is correct then I can be reasonably confident that the sort using that comparison function will be correct as well. On the other hand, I typically don’t test my React components at all (with that type of unit test) because by design they contain very little logic to be verified.
> it can be used in multiple and qualitatively different ways, in contrast with your own position
I agree React can be used in different ways but as I already pointed out earlier, this is a not-particularly-notable characteristic shared by a variety of frameworks, so what you're saying is more of a tangent (i.e. that a tool within a class of flexible tools is itself flexible too) than a contrast to anything I said (i.e. that the bullet points support the idea that React is a framework). In order to contrast to my original comment, the logical thing to do would be to present arguments to support the idea that React is not a framework, since that was the point of contention upthread that I was responding to.
In order to contrast to my original comment, the logical thing to do would be to present arguments to support the idea that React is not a framework, since that was the point of contention upthread that I was responding to.
You originally wrote, in the comment that I first replied to:
- It "calls you" (i.e. you never explicitly call your React components, React does)
This is clearly true, but as we have established, merely having a library call back into your own code is not a defining characteristic of a framework, because we all agree that the sort and Lodash examples aren’t frameworks.
I don’t think you’ve ever properly addressed my point that calling ReactDOM.render may only differ from these examples in terms of scale. You gave an obviously contrived example of importing Ember and not doing anything, but you can import React and use it to render component trees usefully efficiently — its raison d’être — simply by calling render when you need to, without relying on tools like state or hooks to trigger updates.
When people talk about React not being a framework, I believe this is what they usually mean. You can use it as a library just for handling your view rendering — “the V in MVC” — and you can call that functionality on demand and handle any other responsibilities your application has somewhere else using whatever code or libraries you like.
- 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)
I have migrated more than one substantial web UI from another rendering system to React. It was just like swapping out any other library. Most of the codebase didn’t know the previous rendering system existed before and didn’t know React existed afterwards.
I would like to emphasize that this is completely normal software development. Nothing I did during those migrations was novel or specific to front-end web development.
However, you can’t swap rendering libraries like that if you choose to tangle the other responsibilities of your application with your rendering logic, for example by doing other things inside your React components. In that situation, you are no longer separating concerns cleanly, and of course you will have more difficulty if you want to change your rendering library when you have you have tied the rest of your software architecture to it as well.
- It enforces a specific architectural paradigm (components) to the exclusion of others (e.g. MVC, MVVM, etc)
I have built and maintained more than one substantial web UI that used React and yet still had a clean separation of concerns along the lines of MV*. You use React for the V. You use the other parts to handle other responsibilities and to determine what data to pass into the V for rendering using React.
Obviously the “V in MVC” approach emphasizes using React for rendering, while managing other responsibilities like state and remote API calls in other ways. That means you’re not going to be using a lot of hooks, or class component state and lifecycle methods, because you won’t much need them. I’m not arguing that this is the only way you can use React, and clearly some people prefer to use those other React features more and their software designs will differ accordingly.
But contrary to your claim that I quoted above, React does not enforce a specific architecture. It does not stop you from adopting other strategies for designing applications and managing UI code that have proven to be effective elsewhere in the software world, and those strategies can be used as effectively with React as any other rendering library.
Those are the three specific claims you made that I first responded to because I don’t fully agree with them in the context of React. If your argument is that React is a framework because those claims are defining properties of a framework and React has them then evidently we disagree about that conclusion. But IMHO terminology isn’t as interesting as exploring different ways React can usefully be employed and the guarantees it gives and restrictions it imposes in each case, whatever we choose to call them.
> ReactDOM.render is only different to these examples in terms of scale
I think you're confusing longitudinal vs latitudinal comparisons (see point about direct competitors). Sorting is not a competitor to React, or even in a similar class of tools.
I did mention equivalents to ReactDOM.render in various frameworks. Those equivalents also differ in scale in the exact same way as React does to sorting, and yet we're clearly in framework territory when we talk about them. One substantial difference I addressed is that React is inextricably integrated to its internal state and re-rendering mechanisms even if you only use ReactDOM.render; calling setState will always take over control for managing batch updates (not calling it is beside the point; it's always available). This is objectively more "frameworky" than, for example, Mithril's `m.render`, which acts like a true library in this case (and yet, it's still a framework).
> I have migrated more than one substantial web UI
I'm not sure what this has to do with the point you quoted. We've established w/ the lodash example that it is possible to "permeate" the codebase w/ a library, but that doing so is not an intended use case for a project that is a library, and conversely a project that is designed to dominate a complex control flow orchestration between user code and itself throughout a significant portion of scope for an app is a framework characteristic (and React presents that characteristic)
> had a clean separation of concerns along the lines of MV
Here, it appears you're conflating framework-ness with a specific scope size (roughly the size of MVC); this is inaccurate. Consider that Ionic is a framework despite being largely a "only V". Consider that Jest is a framework, despite having no concept of M/V/C whatsoever. Entity is only "M" but also a framework. But what they all have in common is they are organizationally opinionated within their scope of operation (this is what alluded to when I mentioned spaghetti code).
> If your argument is that React is a framework because those claims are defining properties
They are not defining properties, not exclusive to frameworks, possibly not even universal, and certainly not an exhaustive list of arguments. What I'm saying is that these are arguments used to support the idea that React shares an overall strong semblance to frameworks (even if the imprecise examples can be nitpicked to whatever extent). "If it quacks like a duck" and all that.
If we were to zoom out a bit, the entire reason why this whole framework-vs-library thing is even a thing stems from objections that the "just a library" tagline is often mindlessly parroted as a soundbite to support the idea that React is not "bloated". But ironically, if you scroll around this thread, you'll see that this correlation has lost its edge on two fronts: a) people now complain about fragmentation in the React world caused precisely by its lack of opinions in various areas of web development and b) many of the frameworks I mentioned are less "bloated" than React (because they don't support multiple ways of doing components, because they offer equivalent functionality in less bytes, or because apps written in them require less boilerplate for things like state management, for example).
This discussion is frustrating and I feel perhaps we should agree to disagree at this point. It seems we keep talking past each other and I don’t know how else to explain my position. It also seems that perhaps you have some more specific ideas in your head about what some of these terms mean and you are seeing what I’m writing through that lens, but unless you explain those ideas the rest of us can’t productively discuss them with you.
I addressed three specific claims you made in the comment I first replied to. The first was true as written and my point in that case was that it doesn’t tell the whole story about how React relates to your own application code. I have tried to be diplomatic, but the second and third were simply incorrect: you claimed that React permeates your code and makes migration between rendering libraries difficult, and that it enforces a specific architectural style to the exclusion of others, but as I have tried to explain, my own experience on multiple occasions has proven otherwise. These things are only true if you use React in a certain way, and my greater point throughout this discussion has been that you are not required to use React in that way and a different approach may be beneficial in some respects. I feel like the old saying applies here: perhaps someone who says a thing cannot be done should not interrupt someone who is doing it.
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.
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.
I don't know what the true proportion is, but obviously a lot of people do choose to use React that way. This has never been in dispute.
My point is that you don’t have to. You can keep things simple with React and keep responsibilities cleanly separated within your code. If you do, you will never have a lot of the problems that people who choose to tangle everything together within their React components can run into.
This whole discussion is about whether modern web development practices are good practices, so I think that’s a fair point to make regardless of how many people currently choose to do things the other way.
- 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)