GithubHelp home page GithubHelp logo

Comments (18)

rsslldnphy avatar rsslldnphy commented on May 11, 2024

I've managed to find a workaround - is this structure required?

To prevent the unnecessary re-rendering of the child components, I moved the dereferencing of the reactions to the top level component and then only pass down fully-deref'd state to the children. So my top level page component now looks like this:

(defn page
  []
  (let [name  (subscribe [:index/name])
        f1    (subscribe [:index/form])
        f2    (subscribe [:index/form-2])
        state (subscribe [:app-state])]
    (fn []
      (let [name  @name
            f1    @f1
            f2    @f2
            state @state]
        [:div
         [title name]
         [form-1 f1]
         [:hr]
         [form-2 f2]
         [current-state state]
         [:hr]]))))

But reading through the re-frame readme I can't convince myself that this should be required? It looks like you subscribe and deref with impunity in child components?

from re-frame.

mike-thompson-day8 avatar mike-thompson-day8 commented on May 11, 2024

This looks a bit wrong:
https://github.com/rsslldnphy/re-frame-issue/blob/master/src/reframe_issue/views/widgets/form.cljs#L40

My version (untested):

(defn email-field
  [field]
  (let [{:keys [path data]} *current-form*
        value               (reagent/atom (get data field))]
    (fn []                   ;;  <-----    enclosing square bracket removed
       [:input.form-control
        {:name name
         :type "email"
         :value @value
         :on-change #(reset! value (dom/value %))
         :on-blur #(update-field path field @value)}])))

That square bracket in the wrong place meant that every reset! on value (every on-change) was triggering a rerender.

from re-frame.

rsslldnphy avatar rsslldnphy commented on May 11, 2024

Ah, that's actually a hang-over from my attempt to use binding to not have to pass the form object as an argument to every single form element (now abandoned). But that email-field component isn't currently being used in the view.

from re-frame.

rsslldnphy avatar rsslldnphy commented on May 11, 2024

The only ones currently being used in the view are the text-field and label components. And the text-field one looks exactly like your version of the email one I think? Apologies for the slightly confusing example, I've been trying a ton of stuff to get it to work and some of the traces of my attempts are still lingering.

(defn text-field
  [f field]
  (let [{:keys [path data]} f
        value               (reagent/atom (get data field))]
    (fn []
       ; (println "rendering text field")
       [:input.form-control
        {:name name
         :type "text"
         :value @value
         :on-change #(reset! value (dom/value %))
         :on-blur #(update-field path field @value)}])))

from re-frame.

mike-thompson-day8 avatar mike-thompson-day8 commented on May 11, 2024

I've got a feeling this will be a straight reagent-usage issue. The result of a subscription acts just like a standard ratom.
Have a quick look at the use of inputs in the intro docs just above the heading "Essential API".
Other than that can you provide the simplest possible example showing the problem. Thanks.

from re-frame.

rsslldnphy avatar rsslldnphy commented on May 11, 2024

I'm not unaware of how to use Reagent, although I've been bitten by weirdnesses in its behaviour plenty of times - which is kind of what I suspect is happening here. I've pushed the repo again with the code pared back as much as possible while still demonstrating the problem: https://github.com/rsslldnphy/re-frame-issue.

It appears to only happen with the use of the local reagent atom for temporary state (a practice I lifted from your todomvc example). I don't see how a local atom in one component could affect the rendering of a totally separate component that's not even in its ancestors (it's a sibling) though.

from re-frame.

mike-thompson-day8 avatar mike-thompson-day8 commented on May 11, 2024

Okay, try this re-write of what's in here (code untested):

(register-sub
  :index/form-1
  (fn [db _]
      (let [path [:pages :index :form-1]
             data (reaction  (get-in @db path)) ]     ;; reaction #1
    (reaction {:path path :data @data }))))          ;; reaction #2

Remember to also do a similar transformation on the other subscription.

Previously, you had a reaction which:

  1. Would always fire on any change to app-db (why? Because it derefed app-db); and it
  2. Would always generate a new data structure.

That 2nd point is key. Any view using this subscription would get a new data value each time db changed. Which meant the view had to re-render -- the comparison of the old data with the new data is via "identical?" and the new data did not test identical? to the old data. It might test equal, but not identical?.

My proposed rewrite uses chained reactions. The first reaction extracts the value, nothing more. Only if the extracted value is not identical? to the previously extracted value will reaction #2 run. And only then will subscribed views see a reactive update via reaction #2.

Currently, this is all a bit more subtle than I'd like. I have a re-design of subscriptions in the wings which will make it a lot harder to make this kind of mistake. But this is probably still a week away.

Tell me how you go.

from re-frame.

rsslldnphy avatar rsslldnphy commented on May 11, 2024

Ah, nice! That did it. Thanks a lot for your help. I thought it might have been something like this, and had a go at chaining reactions but if I remember correctly I must have set it up either so the inner reaction was recreated each time, or that I derefed the inner reaction in the view when creating the outer one, making it pointless.

You're right, this is pretty subtle :-). The use of identical? in reagent has ended up causing some subtle performance bugs for us. You just have to remember when updating any kind of collection that the way you choose to update (or replace) it can have significant performance ramifications when rendering.

from re-frame.

mike-thompson-day8 avatar mike-thompson-day8 commented on May 11, 2024

Excellent! I'll get the new (less subtle) version out soon.

from re-frame.

mike-thompson-day8 avatar mike-thompson-day8 commented on May 11, 2024

BTW, here's what i do when I'm debugging: I use clairvoyant

I wrap everything (other than ns) in handlers.cljs subs.cljs and views.cljs in one of these ...

(trace/trace-forms {:tracer trace/default-tracer} 
   <everything goes in here, except the ns form>
)

That will show you exactly what is being "run" (in the console). The event handlers, the subs firing, the views re-rendering. The output is a lot better if you give names to your anonymous functions.

I mention this only because I saw this kind of thing:

 (println "Rendering form 1")

in your reagent components.

Just a thought. I certainly find it helpful.

from re-frame.

rsslldnphy avatar rsslldnphy commented on May 11, 2024

Looks great, thanks for the tip!

from re-frame.

rsslldnphy avatar rsslldnphy commented on May 11, 2024

By the way, in case it's of any use to you, I've started using this macro in my views so that I always know I've put the subscriptions outside the inner fn and I never have to deal with deref-ing reactions directly:

;;; Example usage

(defn user-details
  []
  (with-subs [user [:current-user]]
    [:div
      [:h1 (:name user)]
      [:p  (:bio user)]]))

;;; Implementation

(defn- to-sub
  [[binding sub]]
  `[~binding (re-frame.core/subscribe ~sub)])

(defn- to-deref
  [binding]
  `[~binding (deref ~binding)])

(defmacro with-subs
  [bindings & body]
  `(let [~@(apply concat (map to-sub (partition 2 bindings)))]
     (fn []
       (let [~@(apply concat (map to-deref (take-nth 2 bindings)))]
         ~@body))))

from re-frame.

mike-thompson-day8 avatar mike-thompson-day8 commented on May 11, 2024

Yeah, nice. With your permission, I'll add these to the Wiki under the heading of "Macros - work in progress". I'm not yet ready to formalise the macro side of things (but it is coming) but that shouldn't mean others shouldn't get the immediate benefit of your insight.

from re-frame.

rsslldnphy avatar rsslldnphy commented on May 11, 2024

Sure, by all means. And I know I've already said this, but re-frame really is an impressive piece of work. We've tried a bunch of approaches over the last few months (including a single state atom, which we moved away from due to performance problems but have suffered for as a result - reactions were the missing piece of the puzzle here) so I know that distilling things down to such a simple, clean codebase is no mean feat.

from re-frame.

mike-thompson-day8 avatar mike-thompson-day8 commented on May 11, 2024

That's very kind of you, thanks. I feel there's a bit to go with re-frame. But there is a really, really good development, debug and testing story here, I'm sure of it. Please keep the ideas coming.

Here you go: https://github.com/Day8/re-frame/wiki/Macros--WIP

from re-frame.

rsslldnphy avatar rsslldnphy commented on May 11, 2024

I noticed that you added to the macros wiki that if you subscribe but never use the result of a subscription, you'll create a memory leak. Would you mind explaining what you mean by this? I couldn't work out at all how it would make a difference. Surely even if you deref the reaction, you've still closed over it? How does deref'ing free up the memory? Thanks.

from re-frame.

mike-thompson-day8 avatar mike-thompson-day8 commented on May 11, 2024

In the the docs, I say that a reaction returns a ratom but, at one point. I explain this is a white lie. A reaction (a subscription) actually returns something which behaves like a ratom but it is actually one of these

A Reaction will dispose of itself when the number of watches (things derefing it) transitions from 1 to 0. If nothing ever uses a Reaction in the first place (never uses a subscription), then a Reaction never gets to make the 1 to 0 transition because its number of watches stays stuck on 0, and that means it never disposes of itself and that means it memory leaks.

Let's be clear -- this would only occur if there was a bug in your code. An unused subscription is a bug.

Because your macro derefs all subscriptions automatically, they will always make the 0 to 1 transition (something is watching them), which means, in turn, they can make the 1 to 0 transition later, and dispose will get called. So you might still have a bug, because you have an unused, unneeded subscription, but at least there is no memory leak, just inefficiency.

from re-frame.

rsslldnphy avatar rsslldnphy commented on May 11, 2024

Ah, thanks, I missed that bit.

from re-frame.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.