fn-fx / fn-fx

A Functional API around JavaFX / OpenJFX.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Endless changeloop for sliders

expez opened this issue · comments

Support Question

I'm sure I'm doing something wrong, but I can't figure it out. The sliders I create get into an endless change loop if I make changes too fast. It seems like when the new value is put into the view, that triggers the handler again. How can I avoid this?

Here's what I do:

(defn- my-slider-value-changed-handler [my-app-state event-data]
  (swap! my-app-state assoc :my-slider-value (:fn-fx.listen/new event-data)))

(defui MySliderExample
  (render [this {:keys [my-slider-value] :as my-app-state}]
          [(ui/slider
            :value my-slider-value
            :listen/value {:event ::slider-value-changed})]))

FWIW, I tried rounding these values to lower the number of swaps on the atom, but that didn't have any effect.

Hi,
Can you provide a link to full code ? (github repo maybe).
I will try to see if I can reproduce this on my system.

Thanks,
Anuj.

Can you provide a link to full code ? (github repo maybe).

https://github.com/expez/test

Just run -main in the REPL and drag the slider around. It'll instantly take on a life of its own in an endless change loop.

Here's the entire thing, in case my mistake is obvious through inspection alone:

(ns test.core
  (:gen-class
   :extends javafx.application.Application)
  (:require [fn-fx.controls :as ui]
            [fn-fx.diff :refer [defui render]]
            [fn-fx.fx-dom :as dom]))

(defui MyApp
  (render [this {:keys [my-value] :as data-state}]
          (ui/grid-pane
           :alignment :center
           :hgap 10
           :vgap 10
           :padding (ui/insets
                     :bottom 25
                     :left 25
                     :right 25
                     :top 25)
           :children [(ui/slider
                       :id :foo
                       :min 0
                       :max 10
                       :value my-value
                       :block-increment 1
                       :listen/value {:event ::change-my-value}
                       :grid-pane/column-index 0
                       :grid-pane/row-index 0)])))

(defui Stage
  (render [this data-state]
          (ui/stage
           :title "Test"
           :shown true
           :scene (ui/scene
                   :root (my-app data-state)))))

(defn -main []
  (let [data-state (atom {:my-value 5})
        handler-fn (fn [{:keys [event] :as all-data}]
                     (case event
                       ::change-my-value
                       (swap! data-state assoc :my-value (:fn-fx.listen/new all-data))
                       (println "Unknown UI event" event all-data)))
        ui-state (agent (dom/app (stage @data-state) handler-fn))]
    (add-watch data-state :ui (fn [_ _ old-state new-state]
                                (send ui-state
                                      (fn [old-ui]
                                        (dom/update-app old-ui (stage new-state))))))
data-state))

can you try with the should-update? function as in the code below for MyApp.
I don't see the weird behaviour after this change.

(defui MyApp
  (should-update? [this old-props new-props] false)
  (render [this {:keys [my-value] :as data-state}]
          (ui/grid-pane
           :alignment :center
           :hgap 10
           :vgap 10
           :padding (ui/insets
                     :bottom 25
                     :left 25
                     :right 25
                     :top 25)
           :children [(ui/slider
                       :id :foo
                       :min 0
                       :max 10
                       :value my-value
                       :block-increment 1
                       :listen/value {:event ::change-my-value}
                       :grid-pane/column-index 0
                       :grid-pane/row-index 0)])))

Thank you, that solves it!

What does should-update? do? I've looked around a little now and I can't find any other way to answer that question except digging into the code.

The "defui" macro has a default implementation of should-update? that diffs the old and new state for any component and then decides whether to re-render the component.
By giving our own function that always returns false we make fn-fx never try to re-render MyApp

Thanks, @anuj-seth! I've clearly misunderstood the data flow here. I'll elaborate in case anyone else runs into this problem.

I thought it was:

  1. Slider event is triggered
  2. App state is changed
  3. View is updated to reflect the new app state (slider actually moves)

Instead it should be this:

  1. Slider moves AND slider event is triggered
  2. App state changes
  3. View ignores changes to app state because the slider has already moved.