-
Notifications
You must be signed in to change notification settings - Fork 10
component states
Unlike React, states in Respo is maintained manually for stablility during hot code swapping. At first, states is a HashMap inside the store:
(defonce *store {:states {}})
By design, if states is added, you would a tree:
{
:states {
:data {}
:todolist {
:data {:input "xyz..."}
"task-1-id" {:data {:draft "xxx..."}}
"task-2-id" {:data {:draft "yyy..."}}
"task-2-id" {:data {:draft "zzz.."}}
}
}
}
:data
is a special field for holding state of each component.
It has to be a tree since Virtual DOM is a tree.
You also notice that its structure is simpler than a DOM tree, it only contains states.
respo.core/>>
is the "picking branch" function. It also maintains a :cursor
field.
When you call (>> states :todolist)
, you get new states
variable for a child component:
{
:cursor [:todolist] ; generated cursor, nil at top level
:data {:input "xyz..."} ; state at current level
"task-1-id" {:data {:draft "xxx..."}} ; states for children
"task-2-id" {:data {:draft "yyy..."}}
"task-2-id" {:data {:draft "zzz.."}}
}
Then you call (>> states "task-1-id")
and you get new states
for child "task-1":
{:cursor [:todolist "task-1-id"] ; generated cursor
:data {:draft "xxx..."} ; state of task-1
For state inside each component, it's nil
at first.
You want to have an initial state, use or
to provide one.
(defcomp comp-task [states]
(let [cursor (:cursor states)
state (or (:data states) {:draft "empty"})]
(div {})))
By accessing (:data states)
, you get nil
, so {:draft "empty"}
is used.
After there's data in states, you get data that was set.
Then you want to update component s:tate
(defcomp comp-task [states]
(let [cursor (:cursor states)
state (or (:data states) {:draft "empty"})]
(div {:on-click (fn [e dispatch!]
(dispatch! cursor (assoc state :draft "New state")))})))
So (dispatch! cursor state)
sends the new state.
The last step to to update global states with respo.cursor/update-states
.
Internally (dispatch! cursor op-data)
will be transformed to (dispatch! :states [cursor op-data])
.
And then in updater
you add:
(case op
:states (update-states store op-data)) ; where op-data is [cursor new-state]
; other actions
(do store)
Let's wrap it. First we have empty states inside store:
{:states {}}
And it is passed to (comp-todolist (>> states :todolist) data)
,
and then passed to (comp-task (>> states (:id task)) task)
.
In comp-todolist
, (:data states)
provides component state, (:cursor states)
provides its cursor.
Call (dispatch! cursor {:input "New draft"})
and global store will become:
{
:states {
:todolist {
:data {:input "New draft"}
}
}
}
In comp-task
of "task-1", you also get state
and cursor
, so call (dispatch! cursor {:draft "New text"})
you will get:
{
:states {
:todolist {
:data {:input "New draft"}
"task-1-id" {:data {:draft "New text"}}
}
}
}
And that's how Respo states is maintained.