Fork me on GitHub
#re-frame
<
2020-02-12
>
Daniel Hines02:02:24

Hi all. This may be a bit of a stretch, but I'm working Redux-Loops, a port of the Elm architecture to Javascript. If I'm remembering correctly, Re-frame uses a very similar architecture, so I'm hoping you guys help me with a question. I've got an event loop. I dispatch events. Event handlers (action reducers in my case) return side effects (as data) to be returned. Here's my conundrum: one of my side-effects is that I subscribe to some kind of stream (for sake of argument, lets say its a websocket connection that notifies me every time a chat message comes in). How do I handle this within the confines of this architecture? Can i dispatch actions directly from side-effecting code? I could see that becoming messy quickly if not tightly controlled.

mkvlr08:02:52

@d4hines yes, this is how we do it. Why do you think this is a problem? How is it different from an action being dispatched from e.g. a UI event?

Daniel Hines18:02:13

Thanks for the clarification. I think this is just a little different than how I'm used to, but on further reflection it makes sense. I'm used to RxJS and observable streams, where you can directly merge different event streams. But this will work great. Thanks!

mkvlr08:02:32

note that in re-frame event handlers are global though, which makes this pretty easy.

dominicm17:02:13

How can I use a subscription in a callback? I need to get check whether the search term matches. I don't want to trigger a rerender (performance), so I can't use a closure. When I use subscribe it causes a pretty terrible memory leak. 100mb or so for basic use.

manutter5117:02:53

Your callback should fire off an event. The event handler will get the app-db and you can just use get-in to look up the search term.

dominicm17:02:27

No. I can't use an event handler, this is a predicate.

dominicm17:02:44

This isn't an event handler, I have a return value.

p-himik17:02:49

If for some reason that's not an option, you can save the ratom and call cljs.core/-remove-watch on it immediately after you get its value.

šŸ™ˆ 4
dominicm17:02:12

Beats my reaching into the app-db

dominicm17:02:33

I didn't realise that was all the cache cleanup did.

p-himik17:02:44

Actually, it seems that calling -remove-watch requires some Reagent internals know-how that I don't have, because you have to provide a key for it.

dominicm17:02:57

That's what I just hit, yeah.

dominicm17:02:09

Not sure if chasing this makes sense. Feels very internal.

manutter5117:02:32

Can you provide any more details about your use case?

p-himik17:02:40

You can always call the sub handler directly on app-db. Although that would be quite "dirty".

dominicm18:02:20

My sub is pretty much just get k. So I'm just doing that currently. I'm not sure there seems like an official solution to this.

dominicm18:02:51

Given its a hot function, avoiding the subscription machines might be good.

dominicm17:02:26

@manutter51 I'm using a react component, which takes a function predicate prop to determine whether to show an item. This allows me to implement an external search functionality. I don't want to use a closure because it is on a hot path, and running everything twice will tank performance. So I need to be able to statically define my function.

manutter5118:02:34

So hereā€™s a quick thought just for brainstormingā€™s sake: You probably have a handler that updates the value of the search term in the db. Make your own atom someplace else and have the handler update that value also, so they stay in sync, and use your non-ratom atom in your callback.

jahson19:02:06

This is a common approach for inputs, because each change in app-db triggers all layer 2 subs.

manutter5119:02:45

Thatā€™s a good approach, but lately Iā€™ve been storing input values in app-db because then I can make form validation run off of subscriptions, which is pretty cool.

Lu22:02:41

Thatā€™s basically what Iā€™m doing here https://github.com/luciodale/fork but with a pure function rather than subscribing (which will have the same effect in this case)

Lu22:02:20

I really like to keep re-frame out of the input handlers logic .. I just use it for events that involve external components as well .. being on change validation server side, submit events and so on

Lu22:02:16

For all the rest, a simple ratom is more than enough :) just my opinion

dominicm18:02:09

Heh, that's pretty much the reframe appdb I guess.

manutter5118:02:28

ā€œApp-Db Liteā„¢ā€ šŸ˜„

lilactown18:02:40

it sucks that calling the subscription creates a memory leak

p-himik18:02:56

*calling it outside of the component code

manutter5118:02:19

Basically youā€™re just caching out a specific piece of data, without the subscription overhead, as a performance optimization.

manutter5118:02:40

Also worth remembering that if you have to dig into Reagent internals to write code, itā€™s going to take similar in-depth knowledge to maintain it.

šŸ‘ 4
dominicm18:02:05

Doesn't seem like there's a official solution here though.

lilactown18:02:34

I wonder if calling reagent.core/dispose! would remove the leak

lilactown18:02:57

although re-frame might still hang onto the subscription since it does some caching

p-himik18:02:25

dispose! will remove the cache, along with all of the watchers. It will lead to other usages of the same sub not triggering any re-renders.

p-himik18:02:33

Don't do dispose! manually on subs.

shaun-mahood18:02:56

I haven't used it, but it might be worth looking at https://github.com/den1k/re-frame-utils/blob/master/src/vimsical/re_frame/cofx/inject.cljc to see if something like that would solve your problem - it's recommended in the docs at https://github.com/day8/re-frame/blob/master/docs/FAQs/UseASubscriptionInAnEventHandler.md

dominicm18:02:36

That's only useful for effects, which I'm not able to use.

p-himik18:02:41

Oh yeah, it has dispose-maybe. That may be useful.

lilactown18:02:32

this is very relevant to me since Iā€™m using some subscriptions outside of reagent components (react hooks). I want to nip these memory leaks in the bud

p-himik18:02:39

Would a sub change trigger re-render if you use the sub in a hook?

lilactown18:02:13

I have a use-reaction hook that handles that, yes. it uses reagent.core/make-reaction under the hood so maybe Iā€™m OK

lilactown18:02:33

I havenā€™t noticed any memory leaks yet but itā€™s not a very hot path and I havenā€™t tested for it specifically

p-himik18:02:01

You can check if the subs are left in the cache after the component that uses them is no longer rendered.

šŸ‘ 4
p-himik18:02:35

But again, if you do that much stuff (more or less internal) around a simple sub, maybe it's worth it to just access app-db directly.

isak18:02:36

I haven't tried this yet, but what about binding reagent.core/**ratom-context** to nil when you dereference the sub? @dominicm

šŸ™ˆ 4
p-himik18:02:59

That sounds horrific. :)

āž• 4
isak18:02:25

why though? Obviously there is some global context being used in reframe and reagent

p-himik18:02:32

Is *ratom-context* properly documented? Are there any guarantees about it?

isak18:02:08

To me it seems pretty obvious if you think about how a global API like this would be implemented

p-himik18:02:31

Not sure what you mean.

dominicm18:02:02

I'm not even sure what is evil anymore. :grinning_face_with_one_large_and_one_small_eye: I'm curious to at least try that in the morning. I've disconnected for now.

lilactown18:02:24

I think thereā€™s a proposal at the heart of this for a public feature in re-frame that allows one to do this

isak18:02:42

I mean if that is evil, so is re-frame/reagent

p-himik18:02:07

Why? re-frame doesn't use *ratom-context*.

p-himik18:02:13

And Reagent has invented it.

lilactown18:02:16

I think itā€™s a question of public vs. private API @isak

isak18:02:27

@p-himik You must know re-frame builds on reagent, so I don't see how that is relevant

p-himik18:02:18

Library A uses the internals of library A. Library B uses library A. Why do you think that both libraries are evil? The question is not about global context, but about internals.

p-himik18:02:14

If library A uses the internals of library A, that's a-OK. If library B uses the internals of library A (and is not maintained by the same people that maintain A, arguably), that's not good.

isak18:02:24

I'm not saying it would be a blessed perfect solution, but tradeoffs exist

p-himik18:02:44

That's for sure. And I was arguing about the downsides of the solution with *ratom-context*, nothing more.

isak18:02:23

Fair enough, I agree

Wilson Velez21:02:05

hi, is there something similar to event bubbling in re-frame? I have a component that dispatch an event when clicked but I also need to fire another event from the parent of the component? is that possible?

p-himik21:02:37

AFAIK React events are bubbled by default.

shaun-mahood21:02:36

Are you trying to bubble React events, or re-frame events? When you fire the event on your inner component that dispatches [:event1], what are you looking for from the outer component?

Wilson Velez21:02:41

I want to know that the click was made inside a button of the inner component in order to dispatch something (an ajax call) in the parent, the problem is I donā€™t have access to the inner component to modify it

Wilson Velez21:02:58

can I have two reg-event-fx with the same name? one in the inner component and the other in the parent?

shaun-mahood21:02:24

So you have no control over what happens when the inner component is clicked? Or can you choose what event it is dispatching?

Wilson Velez21:02:17

mmm, I donā€™t want the inner component to know nothing about the outside world

shaun-mahood21:02:37

But you can control what events are fired?

p-himik21:02:04

If the child component is using just :on-click or something like that and does not call stopPropagation, you can have :on-click on the parent component, and it will be called when you click on the child component.

[:div {:on-click #(js/console.log "Div clicked")}
   [:button {:on-click #(js/console.log "Button clicked")}
    "Click me"]]

Wilson Velez21:02:06

I could, Iā€™m thinking maybe to add a param to the inner component and that param could be a function

Wilson Velez21:02:49

I hadnā€™t tried that, I thought it wasnā€™t possible

p-himik22:02:59

As I said in the very first message, React events are bubbled by default. :)

Wilson Velez22:02:09

now, Iā€™m filling stupid for asking that, thanks again @p-himik and @U054BUGT4

shaun-mahood22:02:16

Assuming you're passing the on-click function to your component, I would lean towards something like [:outer-component [:inner-component {:on-click #(re-frame/dispatch-n [[:inner-event][:outer-event]])}]]

Wilson Velez22:02:33

yes, you did, and and google it but I didnā€™t find anything

p-himik22:02:04

@U054BUGT4 There's no dispatch-n function in re-frame. Only effect.

p-himik22:02:25

@U9BUENJ1F Having a playground project where you can check whatever comes to your mind is definitely helpful. Often it's also faster than trying to find an answer online. Especially if you have easy access to the sources of all of the dependencies.

Wilson Velez22:02:56

but I could do somthing like this (do (dispath [e1]) (dispath [e2]))

shaun-mahood22:02:04

@p-himik Oh yeah, good point - I think I put it into one of my projects as a simple wrapper. You could take the idea though

p-himik22:02:52

Yes, you could dispatch two events. Although I bet there's something in re-frame docs justifying against such practice. Debatable.

Wilson Velez22:02:18

but I will stick with the :on-click

p-himik22:02:44

Personally, I would argue against using bubbling where you can control every component. Seems shaky at best.

shaun-mahood22:02:14

I think I ended up taking the code from https://github.com/day8/re-frame/blob/97a5c3795820cd37a976b62e2ac3cb1f626d2467/src/re_frame/fx.cljc#L133 and using the same doseq logic in the functions I needed to use dispatch-n in

Wilson Velez22:02:11

so you donā€™t recommend bubbling

shaun-mahood22:02:04

The other thing you could do is pass a parameter to your inner event for it to dispatch later - (dispatch [:e1 [:e2 :e3]]) - then your inner component event has the logic so that it expects it will be dispatching further events, and you have a central place to deal with more complicated situations (if you are expecting them to come up)

shaun-mahood22:02:07

I would stay away from bubbling if I had any other options - re-frame gives you the tools to avoid it for most situations, and I can't see anything but downsides to choosing to use bubbling unless you have no other choice.

Wilson Velez22:02:07

ok, I will implement any of your recommendation instead of bubbling

shaun-mahood22:02:42

Good luck - feel free to ask more if you run into any issues.

Wilson Velez22:02:48

thank you very much to both of you