News for hackers. Stuff that matters.
Re:How UI-driven state increases accidental complexity by _bxg1 on Tuesday March 24, 2020 @10:26PM (#22681452)
The proposed approach is correct for the proposed use-case. The problem is, lots of UI state isn't handsome, core data like in this example. What about storing the width of a resizable panel? Or the state of a dropdown that can be open or closed? Do you create a dozen little flags in your store for these? Or do you put a flag on each domain object (todo list item, for example) that's not actually relevant to your business logic (you don't want dropdown state getting stored in your database)? Typically you'd make this a local flag internal to each component instance, but what happens when something else in your UI cares about it and has to respond to it? And any of these answers will bind it to your UI concerns, in all different ways. In fact, this state is directly UI-pertinent. It doesn't mean anything outside of the UI.

There are no catch-all answers to these questions. In every case, when you decide where to put your state, you're making compromises. Often, very uncomfortable ones. It is my opinion that the most intrinsically hard thing about building and maintaining UIs is managing state. Unlike most software where "state" is really just either data or an implementation detail along the way to producing output, UIs have real, irregular, messy state baked into their bones. State that can't be eliminated by refactoring, because it really means something.

Re:How UI-driven state increases accidental complexity by viklove on Tuesday March 24, 2020 @11:50PM (#22681788)
The way I handle this, at least in React, is to create a context provider that handles my component's view state, then write my UI element as a consumer of that view state by wrapping it in an HOC. Then, if any other component wants to subscribe to the particular user interaction that drives the component I'm writing, they can simply tap into the existing context with an HOC that I've already written for them.

This way view state is kept outside of my global store, my UI component and its state controller is fully composable and extractable (meaning more reusable), and any other UI element within the same context can view and update my UI component as it pleases.

Another great thing about this is that you can scope your view state (or context) however you want. You can put it on the page level, the app level, or really wherever you want. You don't have to worry about cleaning up or resetting state because the context provider will be unmounted when the user navigates away (unlike a redux store).

Re:How UI-driven state increases accidental complexity by runawaybottle on Wednesday March 25, 2020 @02:07AM (#22682341)
Can you describe this implementation with just vanilla js? What’s the gist here, you’re using namespaced events? I’m just curious what this would look like outside of the world of React.
Re:How UI-driven state increases accidental complexity by viklove on Wednesday March 25, 2020 @02:24AM (#22682410)
There are a lot of approaches you can take with vanilla js, but if your UI follows a component architecture that's modeled as a tree (App > Pages > Widgets), what I'm saying is that you take all of your interactive UI components and separate them into two nodes (a parent and a child). The parent P holds the state and provides callbacks to all descendants, one of which is the child C that you are writing. C is a pure component, meaning it holds no state and only invokes callbacks. You can put any number of "display" components in between P and C, but they should still be able to communicate.

Now, if any components you build in the future also need access to P's state, you can easily implement it as a new pure component that is a descendant of P. Since it has access to the callbacks, it will be able to update our original component C by invoking them.

With this setup, your app is basically separated into two "classes" of components, providers and consumers. Each provider holds the state and callbacks specific to itself only, which means you can easily pull out an individual provider and consumer to reuse in a new place (instead of being dependent on a global store).

The biggest benefit here is that future components can interact with P and by extension C without changing any code in P or C. Furthermore, if you want two parallel widgets with independent state, you can just create two instances of P as siblings, with C as descendants of both of them. If you wanted to do the same with a global store, you would most likely have to update your implementation to support parallel state tracking.

Another benefit is that you don't have to "clean up" the state when P is discarded. For example, if you are using a global store, and you navigate to a new page, you have to implement a callback to clear the widget state from the global store or risk loading stale state when the user navigates back. With this approach, since P has been removed from the tree, its state will be discarded along with it.

The only stuff I put in my global store is stuff that is actually relevant to my entire application. For example the user's username and preferences.

Re:How UI-driven state increases accidental complexity by runawaybottle on Wednesday March 25, 2020 @02:29AM (#22682425)
So my initial feeling is that it almost seems like you are reimplementing base React. Isn’t this effectively prop drilling, with a bunch of child components lifting state up to the global parent component (so that they all get updates?). Apologies if I’m simplifying it.
Re:How UI-driven state increases accidental complexity by viklove on Wednesday March 25, 2020 @02:37AM (#22682474)
Well I'm using React so I can't be re-implementing it :)

I'm not advocating for any ground breaking paradigms here, I guess my only point is that the common advice to just throw everything into Redux (global store) is bad advice. You should only put global state into your global store. The common alternative is to silo state and functionality/UI into one component. I'd argue the third alternative, taking the best of both worlds, enables far more productivity. Basically creating mini-Redux stores that are only focused on one bit of user interaction. These are far more reusable, and since they're scoped locally they eliminate a lot of edge cases (and they're composable!).

Re:How UI-driven state increases accidental complexity by nine_k on Wednesday March 25, 2020 @02:58AM (#22682542)
This sounds quite nice. I wonder if proper Redux stores could be used for that (because you can create many of them).
Re:How UI-driven state increases accidental complexity by bryanrasmussen on Wednesday March 25, 2020 @09:48AM (#22684465)
Since he's using the context api https://reactjs.org/docs/context.html not exactly.

Imagine you have a React tree with a 100 node depth. You would then need to walk all the way up those 100 nodes, but using the context api you can put in context consumer at the branches of your tree where it makes sense, so instead of walking up 100 nodes you walk up 3 nodes to input into the global context.

Hope that is a reasonable high level explanation of a thing that can be complicated without experience of it.

Re:How UI-driven state increases accidental complexity by onion2k on Wednesday March 25, 2020 @03:03AM (#22682557)
Contexts were added to React to replace prop drilling.
Re:How UI-driven state increases accidental complexity by pgt on Wednesday March 25, 2020 @08:00AM (#22683746)
Store it in your DB for that user.
Re:How UI-driven state increases accidental complexity by username3 on Wednesday March 25, 2020 @02:07PM (#22686984)
Do you have example code?
Re:How UI-driven state increases accidental complexity by The_Amp_Walrus on Wednesday March 25, 2020 @02:20AM (#22682392)
> lots of UI state isn't handsome, core data

Very true. My interpretation of the article was that it refers to "core data only". If you re-read the article as "How storing core data in a structure that is coupled to the UI increases accidental complexity" then I think you'll find it more agreeable.

Personally I throw stuff like popup state into local component state and the global important stuff into redux. Perhaps the author implicitly assumes that everybody does this.

Re:How UI-driven state increases accidental complexity by _bxg1 on Wednesday March 25, 2020 @10:26AM (#22684800)
Right- like I said, local state is the natural solution in some cases. But it's not always so clean-cut. Maybe dropdowns were a bad example.

Here's one I've run into: say you have a list of things that can be edited and then when you click "Save" it saves them all to the server. Each of those would need to track a "saving" state to drive its loading spinner. That saving state is transient and UI-specific, and it is tied to each instance of a list item in some sense, but it can't just be local to the list-item component because it needs to be a) set as part of global app logic, and b) read by, for example, the "save" button so it knows when you're allowed to click it again.

There's an entire spectrum of these gray cases and there's no single answer for "how far up to hoist things". You're always making compromises. This is one reason I prefer MobX over Redux, because the latter basically locks you into "global state" and "local state" and doesn't allow for a lot of in-betweens.

Re:How UI-driven state increases accidental complexity by runawaybottle on Wednesday March 25, 2020 @02:15AM (#22682374)
Shouldn’t the panel’s width be stored on the Panel component, and all items in it should probably just be width 100%, or styled to fit the Panel via CSS. That’s the last bit of UI state that I’d ever think to move up to the global store.

The dropdown would act in the same way, no? It mostly sets some local state, and your can then derive the data you need from the store (let’s say the drop down was just an on/off filter). But yes, you are right, you would need to think about a way to abstract this out as a data flag and not a ui state flag.

Re:How UI-driven state increases accidental complexity by nine_k on Wednesday March 25, 2020 @02:54AM (#22682524)
Specifically for dropdowns, you usually want to keep their open / closed position in the UI state explicitly, because you want to close an open dropdown when the user clicks on another dropdown.

I frankly think that this complexity is not accidental. It is the real complexity of the UI, which is often not realized. All the little things that need thought and, at least once, an implementation.

Re:How UI-driven state increases accidental complexity by pests on Wednesday March 25, 2020 @07:27AM (#22683593)
The original drop down can close itself when it loses focus. No need go into shared state for this usecase.
Re:How UI-driven state increases accidental complexity by T-hawk on Wednesday March 25, 2020 @11:13AM (#22685306)
Losing focus is itself a stateful change on a UI element caused by another UI element. It's just that the frontend platform like the browser or OS manages that shared state for you.
Re:How UI-driven state increases accidental complexity by _bxg1 on Wednesday March 25, 2020 @01:28PM (#22686656)
Yes, this is key. There are many cases like this (and CSS pseudo-classes, and the drag and drop API, etc.) where the browser platform gives you convenient ways of avoiding the problem. And certainly, take advantage of those where possible. But they don't eliminate the problem in the general sense.
Re:How UI-driven state increases accidental complexity by nosrednehnai on Wednesday March 25, 2020 @12:56PM (#22686384)
> but what happens when something else in your UI cares about it and has to respond to it?

HTML event-listeners like on-blur, on-focus, on-click, etc. handle this very nicely. I've found that they're frequently disregarded in component libraries, unfortunately.

https://developer.mozilla.org/en-US/docs/Web/API/GlobalEvent...

Re:How UI-driven state increases accidental complexity by sj4nz on Wednesday March 25, 2020 @12:45PM (#22686279)
https://github.com/janestreet/bonsai is making some interesting distinctions between "UI state" and "model state". They're kept separate and they're working on utilities to manage the use of composed components.