Rework Actions
See original GitHub issueCurrent Behavior
Recently effects
and reducers
got merged into actions
. Now the way to set the state is by returning an object.
Figure 1a
const someAction = (model, data, actions) => {
return {
state: 'change'
}
}
The issue with that is that it makes the return
functionality unavailable for (former) effects
, which means that it is no longer possible to have actions return some temporary value. This is an important piece of functionality for complex apps.
In addition to that it makes the following patterns harder:
Figure 2a
const actions = {
example: (model, data, actions) => {
// since `actions.fetch()` depends on `model.url` the user is forced to add a reducer for this
// simple example, since `return` would exit the function
actions.setUrl('https://github.com')
actions.fetch()
},
setUrl: (model, data) => ({
url: data
}),
fetch: model => {/*...*/}
}
Figure 3a
const actions = {
example: (model, data, actions) => {
setTimeout(() => {
// won't work, also needs an additional reducer
return {
state: "change"
}
})
}
}
Proposed Behavior
Suppose there’d be a default action. Let’s say setState
or set
that could be called with a model change. Above patterns would become:
Figure 1b
const actions = {
example: (model, data, {set}) => set({
state: 'change'
})
}
Figure 2b
const actions = {
example: (model, data, actions) => {
actions.set({
url: 'https://github.com'
})
actions.fetch()
},
fetch: model => {/*...*/}
}
Figure 3b
const actions = {
example: (model, data, {set}) => {
setTimeout(() => set({
state: 'change'
})
}
}
Advantages
This has the advantage that any state change is always initiated by a actions.set()
call. No more “sometimes return” and “sometimes add a reducer”.
In addition to that it frees return
, meaning that you can now do this:
const actions = {
three: (model, data, {set}) => model.one + model.two,
print: (model, data, actions) => console.log(actions.three())
}
which is amazing for more complex apps.
Meta
Original issue: What about setState?
Issue Analytics
- State:
- Created 7 years ago
- Comments:30 (23 by maintainers)
@jbucaran
Ok that explanation clicks with me 😃 I now think I better understand how you see it.
… but still: reducers didn’t use to be able to call other reducers. In Elm, Msg:s are something more specific than “just functions” – each one represents a singular way that state can be transformed. Losing this sort of ‘pure’ reducer still irks me a little.
Anyway, I don’t mean to derail this issue with my nostalgia for the “good old days” (2 weeks ago? 😉 )
I just mean to say I (personally) feel like something was lost (“elegance”, “purity”… not sure what to call it), and that I interpret @dodekeract’s suggestion as an attempt to bring back some of what was lost, within the frame of this new architecture.
I think it’s pretty simple actually. If I was pushed to compare the current architecture to the original reducers/effects architecture, I would say: if you return something, then it’s like the old reducers. If you don’t return anything, then it’s like the old effects.
In reality, however, here’s how I see the current architecture compared to the original one: we didn’t merge reducers with effects, we simply removed effects and renamed what was left (reducers) to actions, a more broader and reasonable term that boils down to functions.
I don’t see this as hyperapp going out of its way either. Before, a reducer that returned
undefined
, i.e., didn’t return anything, would cause the view to be rendered and also try to mergeundefined
with the current model, none of which makes any sense.Think about it, first,
actions
behave like the old reducers, we merge their return value with the model. We certainly skip any view renders if your ~reducer~ action returns nothing, asmerge(model, undefined)
would be useless, but even if we didn’t you could still use them as effects (they would cause a useless view render however).The only special case is, admittedly, promises, but then everything is “special” about promises in JavaScript land.