The fact that you can have different coding styles and APIs in your project and it will work is proof that it is backward compatible.

I don't think anyone is disputing that React is still backward compatible with the old API. The point is that there is a difference between having two APIs that both still work and having one stable API.

Backward compatibility is implied if your API is stable, but that's only one aspect of stability and there are many other practical questions. Will documentation and tutorials from a few years ago still be relevant? Will someone joining your team need to learn anything different before they can be productive? Will other libraries you have used previously that integrate with React still work with your new code? Will new libraries you might want to use today that integrate with React work with your existing code that uses the old React API?

Having multiple integrations with different APIs or different libraries to do the same job is a form of technical debt, and sometimes it can become quite expensive.

I find complaints about the stability of JavaScript kind of ridiculous. Everybody is literally still transpiling down into ES5 (2009) and most only dropped IE11 this year (2013), the main libraries/frameworks tend to make expert use of semver, polyfill old APIs and release codemods to automaticallly perform syntax upgrades.

Well, there are a lot of things you've said there that aren't quite the whole story.

A lot of modern build processes don't just target vanilla ES5 any more. They'll target something like the most recent two versions of the evergreen browsers and any specific minimum versions of other browsers they need for their particular market. This produces more efficient production code because all major browsers now support much more recent JS natively.

There are also some modern JS APIs that you can't completely polyfill. You have to target a runtime environment that provides real support for those.

Semver is great when it works, but when your culture is to have absurdly deep trees of nested dependencies with hundreds or even thousands of indirect packages involved, things still break often enough to notice.

Major libraries might provide automatic codemods for straightforward, unambiguous changes. However, sometimes you can't divine the original developer intent automatically. Then some manual intervention is required.

I'm interested whether there are any programming language ecosystems that have done this better? As far as I can see Python 2 to 3 caused hard rewrites, etc.

Almost all of them, in my experience. JS, including the ecosystem and culture around it, has by far the worst stability story of any major language I have used.

This is partly a cultural problem. Writing code to last simply isn't part of the mindset or skill set of many people working in web development.

Many tools and libraries we rely on for the most vital functionality are FOSS with at most a small team working on them or maybe just one single person. That means even really successful and widely used projects can easily fade away as those behind them lose interest.

Much of what is built on top is written by people being paid astronomical amounts of money who will job hop in less than two years for even greater astronomical amounts of money. They might make it to senior/staff levels a few years into their careers or maybe become the CTO at a startup, never having either built a new product from scratch or maintained one for 5+ years to see where those decisions they made earlier on come back to haunt them. Then those people are the ones leading and mentoring the next generation of newbies working in the JS community, and the cycle continues.

It's also partly a technical problem. The language doesn't provide much to help with building large-scale software and maintaining it over the long term. What it does provide is relatively recent. The tooling is relatively weak, well behind the state of the art for many other popular languages, which in turn lag the more research-focussed languages where radically different ideas get explored.

I believe a big factor in these cultural and technical problems is the way that you can just change your code and push out a new version any time you want since you have complete control over the servers hosting it. It has also become the norm to depend on online APIs provided by other parties who can do the same. This means concepts like managed releases and support lifetimes and stable interfaces are given less importance.

In striking contrast to all of that, there is code I worked on professionally a couple of decades ago that was written in C and would still compile and run on any modern platform with few or no changes. Likewise, there are professionally developed libraries in ecosystems like Java and .Net, which are platforms widely used for large-scale enterprise software development, that have been around for a decade or more and are still as active as ever. Even in Python, the 2-to-3 shift took place over about a decade, taking the community and the tools and libraries along with it. The language is still mostly compatible with Python 2 code, and there were tools to mechanically update code for most of the main breaking changes, so many of the popular libraries from the Python 2 era are still available and working fine with Python 3.

The only other language I've used where instability was as awful as JS world is Haskell, and that's obviously not a mainstream language in the same sense. It's mostly used by a community who are very open to trying big new ideas and changing how they work frequently, and that's part of its attraction to those people and also probably a reason that ideas from Haskell sometimes drift into more popular use over time but the language itself remains an eccentric choice for production use.