active-group / reacl

ClojureScript library for interfacing with React framework
78 stars 1 forks source link

Inconsistent states when there are two events in one cycle #47

Open dfrese opened 4 years ago

dfrese commented 4 years ago

When two events happen "at the same time" (on one event loop), then a state cannot be updated consistently. That can happen with onClick on both a parent and a child (event bubbling), as in this example where the final state is not 2, but 1, although both events fire and both messages are handled:

(deftest bubbling-events-test
  (let [c1 (reacl/class "c1" this state [& more]
                        render (apply dom/div
                                      {:onclick (fn [ev]
                                                  (reacl/send-message! this :foo))}
                                      more)

                        handle-message
                        (fn [msg]
                          (reacl/return :app-state (inc state))))

        c2 (reacl/class "c2" this state []
                        render (c1 (reacl/bind this :x)
                                   (c1 (reacl/bind this :x))))

        host (js/document.createElement "div")
        cc (reacl/render-component host c2 {:x 0})]

    (let [inner-div (.-firstChild (.-firstChild host))]
      (react-tu/Simulate.click inner-div (js/Event. "click" #js {:bubbles true :cancelable true})))

    (is (= 2 (:x (test-util/extract-app-state cc))))))
dfrese commented 4 years ago

Note that I don't know how this is solvable in the current model, as the link between the app-states is established in the 'render' clause, which is not evaluated between the events (by React). But you wanted to take a look, @mikesperber .

Also note that the same issue exists when changing the state in component-did-mount in the c1 class, which might be more common:

(deftest two-mounts-state-test
  (let [c1 (reacl/class "c1" this state [& more]
                        render (apply dom/div
                                      more)

                        component-did-mount
                        (fn []
                          (reacl/return :app-state (inc state))))

        c2 (reacl/class "c2" this state []
                        render (c1 (reacl/bind this :x)
                                   (c1 (reacl/bind this :x))))

        host (js/document.createElement "div")
        cc (reacl/render-component host c2 {:x 0})]

    (is (= 2 (:x (test-util/extract-app-state cc))))))