I don't think that was ever the claim, the rest of the article is similarly misguided. UI as a function of state doesn't mean "UI is a function of business logic state". It means we define the UI in a declarative way based on the view state.
I don't think that was ever the claim, the rest of the article is similarly misguided. UI as a function of state doesn't mean "UI is a function of business logic state". It means we define the UI in a declarative way based on the view state.
In Cocoa, scroll position is part of the view's state, a mere property of the view. This is simple because the UI itself is stateful.
In React, scroll position is typically not part of the state from which we project the view. Instead this state is attached to the projection itself (e.g. a HTML node) and we are dependent on the "Memoization Map" to preserve it. So this memoization is now required for the correct functioning of the app. The "pure function" abstraction is leaking.
I see the “abstraction leaks” as a feature, not a bug. It means React is flexible enough to handle the real world, which is a lot messier when things go async or interactive. Re-rendering due to mouse position without :hover CSS and friends would be incredibly painful.
Also, I have worked on React apps that tracked scroll offsets. This is common (sadly) when managing back and forward state in a single page app as you might want to show a new page on-click and “scroll to the top” but then click the back button “and scroll back to where you left off” and not have the page actually reload for either of those interactions.
Not saying it’s pretty, but it’s certainly possible to need to track scroll offsets. My preference would be to layer stateful scroll-offset views otherwise rendered by pure functions on top of each other and when you go back, just pop a view off the stack, but not all SPA frameworks are designed that way.
The typical approach would be to attach an event listener to a scroll event or to an IntersectionObserver and then set a state property from that.
Memoization is supposed to be purely a performance enhancement. I would never use it for handling scroll position.
If you define your state broad enough everything becomes a declarative/pure functions. That's not the point of interest, it's that React doesn't (and can't thanks partially to the DOM) handle view state super well. You can't make the complexity disappear so without using side-effects like useState/useEffect it lives either the model (current typed text) or the internal state of the DOM node (scroll position).