I'm a huge functional programming fanboy myself (my first full time job out of college was a Clojure/ClojureScript gig, by deliberate choice), so a completely pure mapping from state to UI sounded like a great idea when I first started diving into the world of UI programming.
Soon however, I realized that in practice (at least in the context of a React-based application), there's little to be gained by storing state that is truly temporal/localized in a persistent/global store (which is necessary for enabling a truly pure mapping of state to UI), and that there's in fact much to lose, because:
1) By nature of being temporal, they're state that needs to be initialized when they need to be used and destroyed afterwards, otherwise we risk exposing stale state to the next usage in time.
The initialization/destruction process often needs to be synchronized with some lifecycle of a component. Animations on mount/unmount, and form input that needs to reset when closing a modal, etc, are great examples of this.
If we used component local state to begin with, that state lives and dies with the appropriate component automatically, with no additional ceremony.
On the other hand, if we were to store that temporal state in a persistent state store, we'd still end up having to create components that hook into component lifecycles to create/destroy that state at the appropriate time in the persistent store manually, which, even if we ignore that it's often a tedious, error prone process, means introducing impure components into our tree of pure components anyways, so we've really gained nothing over the component local state approach.
Not to mention that temporal state is often frequently updated (as an example of an extreme, FLIP animations (https://css-tricks.com/animating-layouts-with-the-flip-techn...) by nature require at least 60 state updates per second), and having a few of these in a persistent state store can wreak absolute havoc on application performance if we mess up even a little bit in our pure rendering optimizations, which is notoriously tricky to get right perfectly due to all the edge cases we have to be mindful of (not creating new event handlers functions with every render is especially tricky in a codebase that's committed to using only "pure" components; in fact, I'm not sure if that's even possible in the strictest definition, since all the techniques I've worked with involve creating an impure class component to memoize the function, and the new useCallback hook likely isn't pure in the strictest sense either).
2) By nature of being localized, they're state that no other component should ever be concerned with.
Making them accessible at all to other components, as we do by storing it in a global state store, is by definition a leak of implementation detail, which over the long term makes it harder to change the components that use localized state, because we have no easy way to guarantee its "localized" state isn't being depended on by something else. In fact, to suggest that they're using "localized" state at all at this point is more wishful thinking than anything else, because nothing exists to enforce this localization once we put it in the global store.
At best they're just extra noise we need to ignore/filter out when debugging/serializing the state of the store, and at their worst they can lead to extremely brittle and over-entangled component trees where changing one part of the tree can inexplicably break seemingly unrelated parts.
On the other hand, component local state is for all intents and purposes, truly localized. We cannot access component local state outside of the component itself unless we explicitly expose it to children as props or to parents by refactoring it up a few levels, at which point we're making the explicit decision to change the locality of that state to include more/different components, and that decision is completely self-evident.
Whenever we decide to use component local state, we can rest assured knowing that their locality is enforced by the React runtime, rather than some handwavy global store access convention that we'd otherwise have to resort to if we stored localized state in a global store, which involves an ongoing cost in terms of enforcement while offering little in the way of real safety.
To be perfectly honest, some/all of the points I mentioned could potentially be attributed to React not abstracting us far enough away from the stateful nature of the underlying platforms it builds upon (the DOM for web, native UI platforms for React Native). For the case of temporal state, I can imagine for instance a potential alternative/higher-level library where React's stateful lifecycle hooks are replaced with some elegant purely functional primitive for supporting the same use cases, perhaps something that models _time_ explicitly as part of state, like Datomic does with transactions (nothing similar comes to mind for handling localized state though, so perhaps encapsulation and true purity are just at odds on a fundamental level).
Though I have not yet seen anything that would enable a truly pure state -> UI mapping for building non-trivial applications while avoiding the aforementioned drawbacks, I'd of course happily re-evaluate my position on the feasibility of this approach when I encounter such a solution.
WRT >there's little to be gained by storing state that is truly temporal/localized in a persistent/global store (which is necessary for enabling a truly pure mapping of state to UI),
Perhaps, I mis-understood, but when I do
.setState({data}, fnToCallAfter)
as a user of React, I do not really know where the state is stored, in other words, I do not store in global/persistent store.
---
I do use React.Context quite a bit to store my LoginState,
my special purpose in memory store (using Immutable.js) that acts as cache for some often used/expensive to get backend data.
When my LoginData, or CacheData get updated, react automagically calls 'render' (and static friends) on all the mounted components that have signed up to observers to the Context's changes.
This is very similar to newly release AndroidX jetpack Lifecycle-aware ViewModel, that calls the observer methods, only on activites/fragments whose lifecycle is 'active' (this reduces complexity of managing android activities during the 'rotation/config' changes.
---
I am just not clear where you run into a situation where you had to use the component-level state management in react, that required you to see the innerworkings (or implement) your mechanism to store component's state.
Soon however, I realized that in practice (at least in the context of a React-based application), there's little to be gained by storing state that is truly temporal/localized in a persistent/global store (which is necessary for enabling a truly pure mapping of state to UI), and that there's in fact much to lose, because:
1) By nature of being temporal, they're state that needs to be initialized when they need to be used and destroyed afterwards, otherwise we risk exposing stale state to the next usage in time.
The initialization/destruction process often needs to be synchronized with some lifecycle of a component. Animations on mount/unmount, and form input that needs to reset when closing a modal, etc, are great examples of this.
If we used component local state to begin with, that state lives and dies with the appropriate component automatically, with no additional ceremony.
On the other hand, if we were to store that temporal state in a persistent state store, we'd still end up having to create components that hook into component lifecycles to create/destroy that state at the appropriate time in the persistent store manually, which, even if we ignore that it's often a tedious, error prone process, means introducing impure components into our tree of pure components anyways, so we've really gained nothing over the component local state approach.
Not to mention that temporal state is often frequently updated (as an example of an extreme, FLIP animations (https://css-tricks.com/animating-layouts-with-the-flip-techn...) by nature require at least 60 state updates per second), and having a few of these in a persistent state store can wreak absolute havoc on application performance if we mess up even a little bit in our pure rendering optimizations, which is notoriously tricky to get right perfectly due to all the edge cases we have to be mindful of (not creating new event handlers functions with every render is especially tricky in a codebase that's committed to using only "pure" components; in fact, I'm not sure if that's even possible in the strictest definition, since all the techniques I've worked with involve creating an impure class component to memoize the function, and the new useCallback hook likely isn't pure in the strictest sense either).
2) By nature of being localized, they're state that no other component should ever be concerned with.
Making them accessible at all to other components, as we do by storing it in a global state store, is by definition a leak of implementation detail, which over the long term makes it harder to change the components that use localized state, because we have no easy way to guarantee its "localized" state isn't being depended on by something else. In fact, to suggest that they're using "localized" state at all at this point is more wishful thinking than anything else, because nothing exists to enforce this localization once we put it in the global store.
At best they're just extra noise we need to ignore/filter out when debugging/serializing the state of the store, and at their worst they can lead to extremely brittle and over-entangled component trees where changing one part of the tree can inexplicably break seemingly unrelated parts.
On the other hand, component local state is for all intents and purposes, truly localized. We cannot access component local state outside of the component itself unless we explicitly expose it to children as props or to parents by refactoring it up a few levels, at which point we're making the explicit decision to change the locality of that state to include more/different components, and that decision is completely self-evident.
Whenever we decide to use component local state, we can rest assured knowing that their locality is enforced by the React runtime, rather than some handwavy global store access convention that we'd otherwise have to resort to if we stored localized state in a global store, which involves an ongoing cost in terms of enforcement while offering little in the way of real safety.
To be perfectly honest, some/all of the points I mentioned could potentially be attributed to React not abstracting us far enough away from the stateful nature of the underlying platforms it builds upon (the DOM for web, native UI platforms for React Native). For the case of temporal state, I can imagine for instance a potential alternative/higher-level library where React's stateful lifecycle hooks are replaced with some elegant purely functional primitive for supporting the same use cases, perhaps something that models _time_ explicitly as part of state, like Datomic does with transactions (nothing similar comes to mind for handling localized state though, so perhaps encapsulation and true purity are just at odds on a fundamental level).
Though I have not yet seen anything that would enable a truly pure state -> UI mapping for building non-trivial applications while avoiding the aforementioned drawbacks, I'd of course happily re-evaluate my position on the feasibility of this approach when I encounter such a solution.