Fork me on GitHub
#re-frame
<
2021-08-05
>
Otto Nascarella12:08:30

quick question: I got a sub :my/sub that has some logic in it. I noticed that the logic gets executed every time `(subscribe [:my/sub]) is called. But I’d prefer the logic to be executed only when input data changed (the sub depends on other subs :<- [:other-sub/i-want-to-be-the-triggerer-for-the-logic] what’s the best way to deal with this?

p-himik12:08:51

Where do you call subscribe and where do you call deref/`@`?

Otto Nascarella12:08:09

I mean… in the view

p-himik12:08:49

Is it in a JS event handler?

Otto Nascarella12:08:49

it’s a push/pull problem. I kinda discovered that reframe is a pull thingy….I am used to push thingy

Otto Nascarella12:08:30

> Is it in a JS event handler? no. in a let block.

Otto Nascarella12:08:49

so every time the view is rendered, it calls subscribe and executes the logic

Otto Nascarella12:08:10

I’d like that logic to execute only when it’s “parent” subs changed

p-himik12:08:12

I'm pretty sure that should indeed be the case, but I gotta double check.

Otto Nascarella12:08:38

otherwise…it means that every time that view is mounted, it would run every logic, of every sub in the graph

p-himik12:08:08

It doesn't happen on my end. How do you check - via code reload or via some data change that re-renders the view?

Otto Nascarella12:08:50

• view A subscribes to :a/sub first time, so it runs the logic • view A is destroyed, and now view B is shown • you click on a button, and view B is destroyed, so view A gets rendered again • view A has a subscribe to :a/sub and that gets ‘processed’ again, even though it’s input subs did not change

Otto Nascarella12:08:06

I managed to “fix it” by using memoize but that does not sound like an amazing way to do things.

p-himik12:08:52

Ah. When a view is unmounted, the subscription that it uses get cleaned up.

p-himik12:08:01

By using memoize you're introducing a memory leak.

p-himik12:08:21

A better way to fix it would be to lift that sub to a common parent of A and B and just pass the value down.

Otto Nascarella12:08:18

> A better way to fix it would be to lift that sub to a common parent of A and B and just pass the value down. but that would make me move logic that belongs in A somewhere else.

Otto Nascarella12:08:27

would it really be a memory leak? as soon input changes…it will GC prev value and substitute it? how would it differ from having an atom and checking it before running the logic?

p-himik13:08:57

> but that would make me move logic that belongs in A somewhere else. At the same time, A and B clearly share some functionality. So another potential way to fix it would be to make a single component out of them or to wrap them together in something that would deal with that common logic. > would it really be a memory leak? The core implementation of memoize never releases the cached data. There are, however, alternative implementations. E.g. this https://github.com/ptaoussanis/encore/blob/master/src/taoensso/encore.cljc#L1981

p-himik13:08:54

> The core implementation of `memoize` never releases the cached data. It might be fine if you have a few known inputs and the overall inputs+data size is insignificant. It is absolutely not fine if you use e.g. mouse coordinates or current time as an input - the cache will just keep on growing.

Otto Nascarella13:08:31

it takes 2 maps. quite large…but they do not change ever.

p-himik13:08:13

Should be fine then. But yeah, the maps will also stay in memory for as long as that def exists, or whatever you end up using to store the result of memoize.

Otto Nascarella13:08:09

those maps are already in the reframe-db…so its not the maps, but their reference, am I mistaken?

p-himik13:08:12

Ah, sure! No problem then. As long as they don't change. Keep in mind that that premise might change in the future. And then memoize might potentially become a very unpleasant issue to encounter and debug.

p-himik13:08:46

Also note that, depending on your computations in the sub fn, memoize might actually be slower than doing the actual computation.

Otto Nascarella13:08:47

sure. I was reading memoize from core. it keeps all states.. now I got what you meant.

Otto Nascarella13:08:39

the computation itself is not expensive. annoyance is that is not deterministic. it’s got a shuffle there.

Otto Nascarella13:08:26

will look into the other implementation. thanks for the tips!

p-himik13:08:37

Oh, hold on. A shuffle sounds interesting when you want to end up having a determined result for some particular input. What I think you should do instead of all the memoize or implicit sub caching shenanigans then is to compute the data on input change outside of the subs and store it in the app-db. And then use subs to simply extract that data - that's it. It can easily be done with a global interceptor that watches for changes at some particular path in app-db and assocs a new value at some different path when it happens.

p-himik13:08:22

The exact same strategy you would use when a view needs to retrieve some data from a server based on some existing data in app-db. But there it would be http-get while you have shuffle.

Otto Nascarella13:08:49

before, this shuffling logic was at the http level, and it was being “cached” in the reframe db. but I that logic should not live there… it does not belong in there. it makes the code confusing. a simple

(defn memo [f]
  (let [empty {}
        !args (atom empty)
        !result (atom empty)]
    (fn [& args]
      (if (= @!args args)
        @!result)
      (do (reset! !args args)
          (reset! !result (apply f args))))))

Otto Nascarella13:08:08

should do what I need alright.