paldepind / functional-frontend-architecture

A functional frontend framework.
MIT License
1.44k stars 87 forks source link

Transient state #14

Open anthonyshort opened 9 years ago

anthonyshort commented 9 years ago

I've been thinking a bit lately about how an architecture like this would solve the problem of local, transient UI state in a tree that can be quite deep.

For example, let's say we want to have some state when an icon is hovered over so we can change the color of the svg element within. We could use an action to change the state at the top-level, but how do we go about storing this type of state? It's state that is relevant only to icon itself, e.g activeIcon: true, not the entire app. The same could be said for the open state of select menus, or whether an accordion-style UI element is open.

If we store this state along with our app state, we have a couple of new problems:

After building and working with Deku for a year or so most of my thoughts are similar to the work you're doing here. If we can keep all state out of the virtual DOM, use bubbling actions to manipulate state, and hold all state outside of the tree, then everything becomes much simpler. I'm struggling to think about:

I'm really curious to know what people who are approaching UI in a similar way to the Elm architecture are thinking regarding these problems. Hopefully I'm missing some really obvious, but I haven't yet tried building anything of real complexity using an approach like this.

paldepind commented 9 years ago

Hello @anthonyshort. It's nice to have you here. Deku is really cool!

You seem to be making a separation between "app state" and "transient state". I don't understand why you see these things so differently (it might be because other frameworks separates them). You probably know it, but the Elm architecture uses the "big state approach", similar to Om. All state is stored in a single big data structure. That includes business state (things you might want to persist), temporary view state and everything else. Conceptually these things are of course very different. But I don't see why we can't handle them equally.

We need to pass this transient state down through functions and components that don't care about it, which makes the functionality of a much deeper UI element dependant on a higher level component (or multiple) passing the state down through the tree. This breaks the "only care about one level down" approach.

Yes. You are right that we have to pass state down through several components. But in a way they will care about it. They won't care about the content but they will care about passing it one level further down the chain. That makes the flow of data explicit and is inherent to the architecture. I don't see how that breaks the "only care about one level down" principle. Consider something like this:

Component1 -> Component2 -> Component3 -> Component4

Each component only know what it receives and what it has to pass on. But Component2 doesn't know if there is 1 or 5 components above it. It also has no idea that there is two components beneath it. This guarantees infinite nestability and loose coupling.

If state updates are kept next to the component, as in this article, then how do we manage updating the same state across many parts of the UI? For example, if I have a project, I might be able to edit that in multiple ways. If we're just throwing actions upwards what component should own the ability to update the project state? My guess is the first common ancestor. This higher-level component could be responsible for updating the state of many different parts of the UI so that the state can be passed back down correctly.

Yes, my answer would be exactly the same. If several components depends on the same data the data will have to originate in a common ancestor since data can only flow down through the functions.

Have you seen the nesting example? It might answer some of your questions.

I think your questions are very interesting and I'm not sure if you will find my answers acceptable. Basically I don't see transient state as something that different from everything else. If you can think of a simple example that would demonstrate your concern I'd like to take a shot at implementing it.

ericgj commented 9 years ago

@anthonyshort great practical questions. These kinds of things I have been struggling with as well, looking at Mercury, Om, Reagent, Elm, and various Flux alternatives.

To me, one nice part about the Elm approach is although there is a single state atom, you mostly don't need to think about it that way. As @paldepind said, you just have to think about what a child component needs from its parent. There are usually two parts to this --

The proof is in the pudding though, and I have yet to build anything 'real' with this architecture, although I hope too soon.

I would like to experiment some more with building some useful components which involve both HTTP interactions and also a good bit of 'transient' state. Two things that come to mind are:

NB. I noticed that evancz recently proposed a new set of abstractions for doing HTTP in Elm which I also want to look at more in depth.

ericgj commented 8 years ago

FYI I've added two examples here, both of which deal with transient state in nested components and asynchronous updates, in case useful to look at (comments welcome).

They both make use of a similar abstraction to Elm's Effects.

On another note, I found this in-depth Redux tutorial useful in terms of thinking about modelling state transitions from the beginning in a moderately complex app (see "Designing The Application State Tree").

paldepind commented 8 years ago

They are great examples :+1:

I'll have to take a look at the Redux tutorial. Flux has a lot in common with the Elm architecture. Though generally it doesn't seem to insist so much on immutability and purity.

anthonyshort commented 8 years ago

Awesome, going to have a read through all of this :) Learning a lot here, thanks everyone.