I've worked with web and mobile app ui development for quite some time and the biggest snag will always be state management. Everytime I think about some state that needs to be shared, I wonder how can I best do it. There's so many damn choices.
Flutter gives you setState, StreamBuilder, FutureBuilder and AnimationBuilder. Third party libraries include MobX, Riverpod, Getit and GetX.
The web gives you events from the get go. Third party include MobX, redux, vuex, flux, and observable store.
These all have their place, but I always ask why is it that changing a value such as doing counter++ so damn hard to propagate in an application?
counter++ invokes an increment action to which relevant listeners will have to be notified and those can "react" to the notification. How the heck does something as simple as this have so many ways to look at it that spawns so many implementations?
Some libraries want you to create a class around the state (counter) and create a function to do the increment. Some auto wiring will be done so that the increment function will notify listeners after it is finished. Other libraries will use timers to check state against previous state, which works well for small applications, and these libraries are often the simplest, least obtrusive. Technically, most if not all state management libraries use a timer to cut down on excessive changes and batch them up.
State Propagation
The problem state management is supposed to solve is state propagation or consistent sharing of state. This sharing may be via parent -> child, siblings and disconnected ancestors.
Look at flutter for instance, setState notifies the widget that a value has changed which would propagate down the widget tree. That's parent to child state propagation. How do you do child to parent state propagation? You can't. If a child wants to change something, it has to go change a store of data that the parent is listening to to which the parent reacts and propagates the change to the child. You have child -> action -> listener -> parent setState.
Think about it, authentication state is global state. You can keep the data wherever you want as long as it is "globally" accessible to any widget necessary. When you logout, that is an action that needs to propagate the change. The action will occur from a child widget.
setState and Event-based state propagation
I use lit-html for web which exposes a setState just like Flutter does. I also use event based state propagation in both meaning elements, non exclusively, can subscribe to events.
Each element will store a copy of the data they require meaning the element can extract and or mutate the data they obtain. Of course the element must be disposed of and recreated, but the data is accessible in someway. Child can lookup a parent element and get the data or look in global app data cache/store.
I stuck closely with setState because it is the easiest for me to think about and there's no magic. If a parent element is passing data to me then there's no reason to call setState in the child. The child just triggers an action. Now if it is something disconnected like account attribute change where there is only a distant ancestor then maybe the child's setState needs to be called from the event handler in order to reread the global state.
A private in memory datastore with subscription
Effectively serves as the application cache with listeners and loading functions.
It's a very simple data structure. It is a hashmap. Anytime a change happens to that hashmap listeners will be notified.
For lit-html, all this means is that a listener function calls setState.
For Flutter, a StatefulWidget sets up the listener functions on the datastore given the keys. The listener function will call setState on the containing StatefulWidget in order for the child to be re-rendered. The widget does provide a builder function so the child widget can get data from the store.
One ephemeral store is used for the application as the global store. All the keys are constants in a class, so there is no mistake about how to reference the data.
I created this version today after thinking about how I did it on the web with lit-html without needing third party libraries for it. setState is a beast and all that is needed is a way to invoke it easily. Now it is as easy as modifying a hashmap and not worrying about cleaning up subscriptions on dispose which I did manually.
What lead to this data structure was past experience on global state and propagation. I would typically build an "AppStore" structure, but always wondered how I could try to propagate changes. I also wondered a lot about how I could do data refreshes and deduplicate any requests for that data into the ongoing request for the same data. Moving from page to page or screen to screen can trigger requests, so it is better to deduplicate these kinds of requests.
I am continually surprised how simple it is and why state management has complex libraries that use code generation and blah.
Conclusion
I don't think I will use any third party state management libraries in the near future for any applications I create since I have this very simple data structure to do everything needed for state management. setState is really all one needs and a way to organize and load your global data.
P.S. I always hated redux because of its complexity. I used it a long time ago and told myself no, just no and wtf is this garbage. Why would anyone use it for a small app, I will never understand.