re-natal icon indicating copy to clipboard operation
re-natal copied to clipboard

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

Open retrogradeorbit opened this issue 7 years ago • 4 comments

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

retrogradeorbit avatar Jul 15 '17 14:07 retrogradeorbit

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

kisakov avatar Mar 16 '18 05:03 kisakov

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.

teesloane avatar Apr 18 '18 16:04 teesloane

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

ivanbulanov avatar Nov 12 '20 17:11 ivanbulanov

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))}])

dbezrukov avatar Mar 02 '21 13:03 dbezrukov