Modifying the state backend-side
In my current project, I am trying to implement a no-JS fallback to the most basic state controllers (ie. the ones that only emit :state). It would work like this:
- The user clicks on a button, which is actually the submit of a hidden form
- The browser calls the current URL as a POST call, with the controller name, the event name and the serialized params in the payload
- The backend instanciates the reconciler, applies the related state controller (that would modify the related state branch), then renders the HTML page and sends it back
But I have two issues:
- First, I cannot actually manage to modify the state backend-side
- Also, I am not sure how useful the resolvers are
Why I cannot modify the state backend side
The current implementation of the Resolver is written such that it will always resolve the state branch with the initially given resolver function:
;; ...
clojure.lang.IDeref
(deref [_]
(let [[key & path] path
resolve (get resolver key)
data (resolve)]
(when state
(swap! state assoc key data))
(if reducer
(reducer (get-in data path))
(get-in data path))))
;; ...
By the way, this is also clearly stated in the doc.
There is no way to bypass that, except by instanciating a new Reconciler, with resolvers that return the modified state branches - which looks definitely smelly.
Can you see any other way to do that, with the current implementation of Citrus?
Also, a solution would be to first check if the related state branch is already in the state, before calling the resolver:
;; ...
clojure.lang.IDeref
(deref [_]
(let [[key & path] path
resolve (get resolver key)
data (if (contains? state key)
(get state key)
(resolve))]
(when state
(swap! state assoc key data))
(if reducer
(reducer (get-in data path))
(get-in data path))))
;; ...
Which by the way would deal with caching issue at the same time, and allow me to transform the state by just reset!ing the state atom.
Why using resolvers at all
I understand that the purpose of resolvers is to only load data that will actually be used in the UI. But the way I see it, I think it's not the best design:
- I have to deal with cache by myself
- I cannot concurrently load different data branches
So the code can become quite verbose, to have something that is not necessarily done the best possible way.
Meanwhile, if Citrus would simply skip this feature:
- I'd have to only load the data I want (which looks less good on paper, I admit)
- I could load my data the way I want (we already do this here, by the way)
- I would simply give the new reconciler the initial state or a fed atom
The state would no more be lazy, which would make manipulating it way easier. What do you think about it?
Quick follow-up, I finally use this code (which still seems a bit verbose, just to update a bit of data):
(defn reduce-reconciler
[reconciler {:keys [controller-name event-name params]}]
(let [resolver (-> reconciler :resolvers controller-name)
data (if (fn? resolver) (resolver))
controller (get controllers-map controller-name)
result (if controller
(controller event-name params data))]
(if (contains? result :state)
(citrus/reconciler {:state (:state reconciler)
:resolvers (assoc (:resolvers reconciler) controller-name (constantly (:state result)))})
reconciler)))