Fork me on GitHub
#reagent
<
2022-10-09
>
jcb10:10:36

I'm trying to figure out how to make a component respond to events other than user interaction i.e idleness and am wondering if there's a more appropriate way to do the following -

(defn countdown []
      (r/with-let [seconds-left (r/atom 60)
                   timer-fn     (js/setInterval #(swap! seconds-left dec) 1000)
                   arbitry-thing (atom false)
                   ]
                  [:div
                   [:div {:on-change (if (<= @seconds-left 55) (timer-handler @seconds-left "time has elapsed" (reset! arbitry-thing true))
                                                                (js/console.log @seconds-left))} "Time Remaining: " (str @seconds-left "" @arbitry-thing)]]
                  (finally (js/clearInterval timer-fn))))
while this works, I get warning from React, stating that on-Change listener is returning a bool rather than a function

valtteri10:10:16

The value of :on-change should be a function. It’s now being set to the value returned by the if expression.

valtteri10:10:07

You can for example put a hash # in front of the if-expression to make it an anonymous function.

valtteri10:10:03

However I don’t know what would be changing the :div in this example. I guess you’re attempting to just display different content depending on how much time has elapsed. You don’t need :on-change for that. You can just wrap the hiccup you want to show into the if-expression.

valtteri10:10:29

There’s actually a timer example on the reagent docs front-page https://reagent-project.github.io/

p-himik11:10:22

To add to the above comments - if you need to know whether the user has been completely idle, i.e. no keyword or pointer events (which include mouse and touch events) on the page at all, then you need to listen to all those events. For that, you need to use addEventListener on either js/document or js/window with its capture option set to true - otherwise, event handlers that use stopPropagation or that don't bubble for some other reason (like scrolling a scrollable container) will not reset the idle timer.

jcb13:10:17

Yes, the whole scenario here is that the timer would not be static like this and would be reset with user interaction, like a click etc. It's not really the timer itself that is the question, it's just where to fire off a function (that is looking at a variable, in this case an atom) that isn't changing the layout of the component.

p-himik13:10:38

Well yeah, that's exactly what the aforementioned timer example does, so seems like you have all the building blocks you need.

jcb13:10:31

Grand, thanks. I just wondered if there was a better place to do that kind of work. The :on-change didn't feel like it would work generically. I don't think that timer example touches any of that.

p-himik13:10:35

That why I wrote that comment about addEventListener. What you want to achieve has to be outside of any :on-change or anything like that because there's no built-in declarative way to have capturing event listeners.

jcb13:10:43

Yes, I'm wondering if an eventlistener is actually the correct way to track that value at all even though it works. Really I'm looking at the value of the atom rather than the change of the text in that div.

p-himik13:10:58

No, hold on. It's not for tracking the value. It's for resetting it back to 0 upon any user interaction. At least, that's how I understand your usage of the word "idleness". So you have a timer somewhere that, when it reaches e.g. 1000 seconds, does something. And then you have all those capturing event listeners that simply reset that timer.

jcb13:10:34

Ok, I was just using this as an example, for a wider concern. But in this case, I see what you mean.

jcb13:10:23

Thanks for your time @U2FRKM4TW & @U6N4HSMFW!

👍 2
jcb14:10:39

Sorry just to follow up, I don't actually understand where to manually add an eventlistener to a component then

p-himik14:10:18

In the root component. Make it a form-3 component and add all the event listeners in its :component-did-mount lifecycle function and remove the events in the :component-will-unmount - even though in your case it seems like it's not necessary to remove them at all, it's still a good habit. Alternatively, instead of using a form-3 component, use reagent.core/with-let with a finally clause.

jcb14:10:10

ah great, when reading through everything, it kind of felt like those kind of components were referred to only before hooks came along and that they weren't really to be used anymore.

p-himik14:10:42

Nah, not really. I still haven't used a single hook in any of my projects. :)

p-himik14:10:17

At least from what I know, React hooks largely solve problems that have been solved by Reagent quite some time ago.

1
jcb14:10:39

Im trying to find an example of grabbing a specific component but could I use a ref rather than the class

p-himik14:10:47

Not sure what you mean.

Ben Lieberman22:10:00

I have an event listener on window that makes a request to my server on page load. It works like I want it to. But I have routing set up with reitit.frontend and if I navigate to another page and then return, the server call is not performed again. I am using r/with-let and my event listener is registered inside the block. Is that the problem?

p-himik22:10:29

Assuming your router is set up correctly, you have a proper SPA - meaning, the tab/window is not reloaded upon navigation, so the event listener is not fired. However, you can listen to a different event. If your router is set up to use hashes, you can listen to https://developer.mozilla.org/en-US/docs/Web/API/Window/hashchange_event If it's set up to use HTML5 history API, then https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event

p-himik22:10:46

Alternatively, maybe you're using venantius/accountant along with reitit - then, I believe, you can set it all via a single function, :nav-handler.

Ben Lieberman23:10:38

I'll give Accountant a try. Thanks!

wevrem13:10:21

See also https://cljdoc.org/d/metosin/reitit/0.5.18/doc/frontend/controllers. You can dispatch events (to make server requests, etc.) from the start and stop controllers associated with each route.