drapanjanas / re-natal

Bootstrap ClojureScript React Native apps
MIT License
1.22k stars 100 forks source link

Flickering components when not using dispatch-sync and reagent.core/flush #124

Open retrogradeorbit opened 7 years ago

retrogradeorbit commented 7 years ago

OK. This is not really a bug with re-frame, but an issue with React Native and Re-frame (and possibly normal react).

When I have a component, and on change I use a dispatch to alter the atom, and the value of the component is taken from a subscribe, when I change the component, the component flickers back and forth twice to get to the new value.

Lets say I have an input field. And the value of the field is "fooba", and I type "r". What I see in the component is fooba -> foobar -> fooba -> foobar.

After a bunch of digging what is happening is this:

  1. the component has the value 'fooba'.
  2. I type 'r'. The component itself is updated by react to 'foobar' and we see this change (no dispatch has been processed or atom has changed yet)
  3. react/reframe sees that the component is 'foobar' but the value it is set to is 'fooba', so it resets the component to 'fooba'
  4. the dispatch finally is processed, this sets the atom and the value to 'foobar' (but its not displayed yet)
  5. react/reframe sees the component state is fooba and should be foobar, and so sets it to foobar.

OK. changing the dispatch to a dispatch-sync is not enough to prevent this from happening. I have to use a dispatch-sync and then follow it with a reagent.core/flush, and then the flickering stops.

Here's some example code with a react native picker:

[picker {:style style-text
         :selected-value @(re-frame.core/subscribe [:picker-value])
         :on-value-change (fn [val index]
                                (re-frame.core/dispatch-sync [:set-picker-value val])
                                (reagent.core/flush))}
       [picker-item {:label "one" :value "one"}]
       [picker-item {:label "two" :value "two"}]]

So my question is, is this the best way to be doing this? Has anyone else experienced this? Is there a more elegant solution that doesn't involve the manual reagent.core/flush?

I have also asked this question on the re-frame issue tracker: https://github.com/Day8/re-frame/issues/368

kisakov commented 6 years ago

I have the same problem with r/atom. @retrogradeorbit did you find how to solve it? Thank you in advance.

teesloane commented 6 years ago

I've had a few issues with text-inputs in react-native/reagent/re-natal. Generally my stuff ends up looking like this to get things working:


(let [form-val      (r/atom "")
      handle-change #(do (reset! form-val %)
                         (r/flush))]

  (fn []
    [view {:style (classes :container-xlg :mt2)}

     [view {:style (classes :mt2)}
      [custom-input {:placeholder    "Name"
                     :value          @form0-val
                     :on-change-text handle-change}]]])))

This comment pointed me towards using r/flush, and it seems to do the trick for now. It is perhaps inelegant, but I believe it has to do with the way that reagent renders asynchronously via requestAnimationFrame... or something like that.

You can get away with not using controlled components and just storing the value into a non reagent-atom and then pull values out of that when you need to submit the form, but you can't clear the inputs programmatically.

ivanbulanov commented 3 years ago

This issue happens to me on Android with a Picker. dispatch-sync and flush get rid of the issue.

dbezrukov commented 3 years ago

Reproduced in ReactNative iOS, (reagent/flush) did the trick.

(defn- my-text-input
  [{:keys [value on-change-text]
    :or {on-change-text (constantly nil)}}]
  [rn/text-input {:value (or value "")
                  :on-change-text (fn [text]
                                    (on-change-text text)
                                    (reagent/flush))}])