Hi!
Here and there there has been some discussion on some problems and potential solutions around managing state when it gets complex. Possibly related topic: how to handle state for custom components (since hyperapp doesn't allow local component state like React/Vue)
I just wanted to open this issue as a central place to have such discussions.
Here are a couple of scenarios where I think that the initialization, modification or reading of state could use some improvement somehow (feel free to add to this list!!)
I'm hoping we can distill down from this either some best practices, or some more specific key things that make these situations difficult, so that some simple yet broadly helpful solutions can be designed.
When you have a list (array) of "things", where each thing has some state of it's own. Say you're building an email client. Your state would likely be emails: [ {...}, {...} ...] where each object in the list has a bunch of properties like: {body: ..., attachments: [], .... recipients: [] ...., etc...}
When you have some complex conditions for rendering a thing. A lengthy logical formula obfuscates the intent and makes things less readable. Especially if the same logic formula needs to be used in many places of your view.
When state gets deep, your actions need to be defined in ways like (state, actions, {index, jndex, value}) => { state.foo.bar[index].baz[jndex] = value; return state; }. You don't have much choice but to modify the current state (rather than return a new modified copy). It would be nice to have a helper to abstractly so you could just write: (state, actions, {index, jndex}) => stateMutateHelper('foo.bar.index.baz.jindex', value).
Come to think of it -- I think an email client would be a good example project to:
It would be a pretty big undertaking though-- one that I'm not up for, personally (not solo anyway). So if anyone has any ideas for a simpler app that still makes use of complex state, please suggest it!
The most requested state management feature (on Slack anyway) has been the notion of scoping actions to work on subsets of the state (aka "scoped actions")
This gist: https://gist.github.com/zaceno/239e384dd914f1cb83a4d4b36af25ea2
shows an example of how I see that working (several examples in successive refinement -- according to myself anyway)
UPDATE:
Taking it as far as I do in the example above may not be a good idea. By the time we get to the loosely coupled apps, we've basically got stateful components with public interfaces to compose them. Not that that's a bad thing. It's just a significant departure from the basic Elm architecture.
UPDATE 2:
The main reason scoped actions are appealing to me (and others I suspect) is to make reading/writing on deep state less verbose. Perhaps the simplest 'solution' is to leave it up to the user to decorate their actions to achieve this.
It's certainly possible to write a helper that could work like this:
~~~~
actions : {
....
myDeepAction: stateScoper('foo.bar.baz', ({bing, bong}) => ({bing: bong, bong: bing})
....
}
/*
myDeepAction transforms a state from:
{
foo: {
bar: {
baz: {
bing: 'aaa',
bong: 'bbb',
...
},
...
},
...
},
...
}
... to:
{
foo: {
bar: {
baz: {
bing: 'bbb',
bong: 'aaa',
...
},
...
},
...
},
...
}
*/
~~~~
This codepen illustrates how to use the Meiosis pattern to have scoped models, actions, and views while still having one single state for the app, and a tracer for time-travel :) Hopefully this is helpful to the discussion!
@foxdonut I think it is helpful. What I take from it is: the "nestComponent" function is one way that scoped actions could be implemented. I'd be interested in seeing how writing an app in hyperapp would look with that implementation.
@zaceno Yes, that would be a good start. 馃槃 /cc @foxdonut
I'm going to close this issue, because the discussion on this topic has come farther elsewhere (for example here: https://github.com/hyperapp/hyperapp/issues/219)
Thanks @zaceno!
Most helpful comment
This codepen illustrates how to use the Meiosis pattern to have scoped models, actions, and views while still having one single state for the app, and a tracer for time-travel :) Hopefully this is helpful to the discussion!