Comments (18)
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.
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.
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.
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.
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.
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.
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:
- Would always fire on any change to
app-db
(why? Because it derefedapp-db
); and it - 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.
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.
Excellent! I'll get the new (less subtle) version out soon.
from re-frame.
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.
Looks great, thanks for the tip!
from re-frame.
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.
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.
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.
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.
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.
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.
Ah, thanks, I missed that bit.
from re-frame.
Related Issues (20)
- [Enhancement]: Allow nil to be used as a value for :fx HOT 5
- [Enhancement]: Warn on `subscribe` being used outside of a reactive context HOT 3
- [Enhancement]: Allow enrich to return `nil` HOT 7
- [Enhancement]: Allow subscribe to be safely called from anywhere
- [Enhancement]: Add the UseASubscriptionInAJsEvent page to the FAQs menu in the docs HOT 1
- [Bug]: de-referencing re-frame.db/app-db inside subscription counts as changing data? HOT 1
- [Bug] [Docs]: Simple app example docs out of sync with code in repo
- [Bug]: CSS link path changing with URL HOT 1
- [Enhancement]: Adding syntax sugar :<= for input subscriptions taking the same query vectors. HOT 2
- [Bug]: fix examples projects lockfiles warning HOT 2
- [Enhancement]: Warning on re-registering subs / events (ie name collision detection) improvements HOT 4
- [Enhancement]: reg-event-fx :fx shorthand HOT 3
- [Bug]: Multiple usage of the same standard interceptor creator in global doesn't work HOT 2
- [Enhancement]: Subscriptions that return `nil` or `false` are evaluated regardless of what's in cache HOT 3
- Wenn ich etwas vom Planeten entferne oder davon etwas abbauen, muss ich auch etwas wieder zurückführen damit es im Gleichgewicht bleibt! HOT 1
- [Bug]: Reframe 10x is not being opened with ctrl+h after 1.6.0 HOT 1
- [Enhancement]: Upgrade React dependency to React 18 HOT 3
- Steigende Preise: Die einzige Lösung hier erklärt... HOT 1
- [Bug]: Bump Reagent version up possible?
- Calling `subscribe` in a render fn clashes with reagent optimizations HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from re-frame.