lucywang000 / clj-statecharts

State Machine and StateCharts for Clojure(Script)
https://lucywang000.github.io/clj-statecharts/
Eclipse Public License 1.0
229 stars 15 forks source link

`assign` result is merged into state, instead of applied #4

Closed dpetranek closed 3 years ago

dpetranek commented 3 years ago

I've got a state machine:

(defn validate [{:keys [username password]}]
    (-> {}
        (cond-> (empty? username) (assoc :username :required))
        (cond-> (empty? password) (assoc :password :required))
        (not-empty)))

(def simple-sign-in
    (fsm/machine
      {:id :simple-sign-in
       :initial :ready
       :states
       {:ready {:on {:submit :validate}
                :states
                {:error {:exit (fsm/assign (fn [state _] (dissoc state :errors)))}}}

        :validate {:entry (fsm/assign (fn [state event]
                                        (let [errors (validate event)]
                                          (cond-> state errors (assoc :errors errors)))))
                   :always [{:target [:> :ready :error]
                             :guard (fn [state _] (:errors state))}
                            {:target :submitting}]}

        :submitting {:on {:submit-success {:target :ready
                                           :actions (fn [_ _] (println "dispatching submit success"))}
                          :submit-failure {:target [:> :ready :error]
                                           :actions (fn [_ _] (println "dispatching submit fail"))}}}}}))

(as-> (fsm/initialize simple-sign-in) $
    (fsm/transition simple-sign-in $ {:type :submit :username "" :password ""} )
    (fsm/transition simple-sign-in $ {:type :submit :username "a" :password "b"} ))

Instead of proceeding to :submitting after the second transition (because we fixed the validation errors), we instead end up back in [:ready :error]. The problem is in the :exit action on the [:ready :error] state. assign takes the result of the and merges it into the existing state. This means that it is impossible to remove a key from the context.

There's an easy workaround, which is to add nil as a value for that key, but I think it would work more intuitively if the assign-ed function was instead applied to the existing context to produce the next state value.

lucywang000 commented 3 years ago

Thanks for reporting this. Fixed in b4b50c059074b22c7007c8e7ce15c56a41c1d544