how do I use use-effects* to handle re-size screen?
is that correct?
(defn resize [{:keys [set-state]}]
(set-state assoc :height js/window.innerHeight))
(hooks/use-effect*
(fn []
(js/window.addEventListener "resize" #(resize {:set-state set-state}))))
set-state came from
let [[state set-state] (hooks/use-state {:height js/window.innerHeight
:pagina :login})]
@fabrao check out the helix docs https://github.com/Lokeh/helix/blob/master/docs/hooks.md#doing-side-effects
I would also look at how people implement that in ReactJS, as it will be very similar in helix. here’s a blog post I found that seems to have a good solution: https://dev.to/vitaliemaldur/resize-event-listener-using-react-hooks-1k0c
sorry about asking this kind of question but sometimes javascript is very confuse, and mixed with clojurescript (oh God). So,
(use-effect
[foo bar]
(js/console.log foo bar))
what foo
and bar
can be?they are dependencies, which will tell use-effect
to run when foo
or bar
change
where they came from?
i made them up
in case of resize, just use
(use-effect
:always
(set-state assoc :height js/window.innerHeight))
?that will run every single time your component renders
so it will render, set the state, which will trigger a render, which will set the state, which will trigger a render….
you might end up in an infinite loop
so, in this case of "resize" event, how to handle this?
instead of using (js/window.addEventListener "resize" #(resize {:set-state set-state})))
?
you’ll want to do something like that
use-effect
is what will setup and tear down the subscription
see in that way, I think my code is correct
I don’t know what your code looks like right now, but the code you pasted using use-effect*
was not correct
it will also run every render, so every time your component renders it will add a new event listener
I only changed to this
(hooks/use-effect*
(fn []
(js/window.addEventListener "resize" #(resize {:set-state set-state}))
(js/window.removeEventListener "resize" #(resize {:set-state set-state}))) [])
why not?
ah see you changed it 🙂
couple things you still need to do
removeEventListener
has to be given the same exact function that you added
look at this
function useCurrentWitdh() {
// save current window width in the state object
let [width, setWidth] = useState(getWidth());
// in this case useEffect will execute only once because
// it does not have any dependencies.
useEffect(() => {
// timeoutId for debounce mechanism
let timeoutId = null;
const resizeListener = () => {
// prevent execution of previous setTimeout
clearTimeout(timeoutId);
// change width from the state object after 150 milliseconds
timeoutId = setTimeout(() => setWidth(getWidth()), 150);
};
// set resize listener
window.addEventListener('resize', resizeListener);
// clean up function
return () => {
// remove resize listener
window.removeEventListener('resize', resizeListener);
}
}
it will add listener every render tooyeah I think that blog post is wrong
I just picked the first one from google that looked sort of correct 🙂 my bad
your code right now will create a new function #(resize {:set-state set-state})
which won’t be the same as the one you used with addEventListener
that´s the only way to pass state to function
it also needs to return a function that removes the event listener. the code you pasted will add and then immediately remove the listener
you need to store the function you pass to addEventListener
so that removeEventListener
uses the same function
oh, I see
the problem is having set-state
(hooks/use-effect
:once
(let [resize-handler ,,,]
(js/window.addEventListener "resize" resize-handler)
#(js/window.removeEventListener "resize" resize-handler)))
hum, now I see the way to do this
thank you so much
for helping me
sure thing 😄
why are you create this helix?
to hire people from javascript?
🙂
if not, that´s I´m doing migrating from reagent
I wanted to use React Hooks and 3rd party React components easier
and have a library that would work with future React features by default
I use reagent at work, we are slowly adopting helix
Hello @lilactown - does this mean that you are using helix + reagent in a single codebase? If so, did you discover anything that made that awkward? I’ve built a prototype app with helix recently and really enjoyed it (thanks!), but my current production app is entirely reagent. Pretty tempted to migrate to helix if I can do it piece-by-piece without too much hassle.
two awkward things I’ve run into were: • you can’t call a helix component and a reagent component the same way, which often requires you read the code to know • socializing and onboarding people to helix the really nice thing is that reagent components can just return React elements, so you can use your helix components inside reagent just fine.
(defn my-reagent-component
[]
(let [count (r/atom 0)]
(helix.dom/button {:on-click #(swap! count inc)} @count)))
and we created a simple helper to render reagent components to react elements:
(defnc my-helix-component
[]
($r my-reagent-component))
Ah, okay - that makes sense. Definitely very tempted now. The two products that we use reagent for are using material-ui for UI components, and working with material-ui was so much simpler from helix. I’ll have to replace the parts of re-frame that I use, but I don’t think that will be too bad (using datascript on the client, so not really getting much of the re-frame benefits around subscriptions etc.). > socializing and onboarding people to helix I won’t have that trouble, it’s just me to onboard! Thanks for the reply and thanks a lot for your work on helix.
nice 🙂 I’d love to chat at some point about how you’re using datascript… I’m down a rabbit hole right now trying to figure out a nice way to grapple with data fetching / caching and I’ve been looking at datascript, diving into pathom now
Well, for my apps that’s all a bit ad-hoc at the moment. Roughly my apps work like this: trigger event in re-frame -> HTTP calls to our API/third-party API/firestore -> do a bit of transformation on response to match datascript schema -> generate transaction data. No comprehensive story for caching/fetching etc like you get (I think?) if you use something like Fulcro (or in the JS community, a GraphQL client like Apollo). I do wonder if I’m missing out on something that can remove the manual code I need to write around fetching/caching. I will admit that I’m not very current on the other clojurescript approaches to that stuff - I’ve been building client-side apps with the re-frame/datascript/reagent combination for a long time now. That’s all related to the client-side as well, which might not be the whole-scope of the problem you are diving in to? Happy to answer any questions you have about this stuff though.
I am mainly interested in client-side fetching and caching right now. one thing I’ve struggled with is knowing when to re-render a component based on a change to data that a query depends on. how do you handle that right now?
At the moment I use re-posh (https://github.com/denistakeda/re-posh), mostly separating components into presentational & container components in order to avoid expensive re-renders. It works well enough, but does require care regarding performance. I did attempt (a long time ago) to write a wrapper around the datascript entity API to have reagent track entity data access and then trigger re-renders based on that, but didn’t get very far. I don’t think there’s currently any simple way of determining which queries would result in a different result (and therefore a re-render), apart from either a perform-query-and-compare (slow) or heuristics-based approach (re-posh). There is some interesting differential dataflow/reactive datalog stuff being worked on (you’ve probably already seen), but nothing packaged ready for use just on the front-end when I last looked. I also don’t think that the datascript queries are that needed when writing the UI components (still very useful in my effect handlers though), so I would like to try making navigating datascript through entity/indexes easier (wrapping the datascript entity API was one attempt at this). One idea is maybe formalising the container/presentational split a bit, and writing a macro or something to make using it convenient. The “container” side of that would be responsible with walking the datascript graph via entity/index lookups, checking if the result changed, then re-rendering only if it differs. Navigating the datascript that way is fast, so probably not a problem to re-run those on database changes unless the datascript data is changing very frequently. Really I use datascript mostly for the write-side though - it makes managing & structuring the data a lot easier (normalisation based on schema, easy to do upserts, easy to test at the REPL because transactions are data etc). So, no easy answer from me on that.
a lot of our developers working on the front-end are JS developers too so
it all sort of came together 🙂
Hello Will, I tried the code you showed me about :once
but nothing happen. So I tested with (hooks/use-effect :once (js/console.log "passed here"))
and the message never happen too
looking at the code about hooks https://github.com/Lokeh/helix/blob/fd824cdbb9efeee9f8689d4177ee5a043a00b5c5/src/helix/hooks.cljc#L152 you see #?(:clj ...
for use-effect
. So, I don´t know it will run in .cljs
so I did
(react/useEffect
(fn []
(let [resizer #(set-state assoc :height js/window.innerHeight)]
(js/window.addEventListener "resize" resizer)
#(js/window.removeEventListener "resize" resizer))) (into-array []))
and checked in Browser event listeneronly one listener
if (use-effect ,,,)
doesn’t work but (react/useEffect ,,,)
does, that sounds like a bug
if you can show me a minimal reproduction then I can try and see how to fix it
the definition of use-effect
is in a :clj
branch because it is a macro. macros are run in the compiler, which is a Clojure program, not in the generated JS. the output of the use-effect
macro is basically (react/useEffect ,,,)
so it should work the same
@lilactown hello, how you doing? hey man, I was playing with hooks and react, I like the way they compose very much, but that interface of [val setter] (use-state 3)
, this kind bothers me in the sense that's 2 separated things, so I was playing with the idea of wrapping that in a atom
interface, the implementation is quite simple in the end, something like this:
(defn use-state
"A simple wrapper around React/useState. Returns a cljs vector for easy destructuring"
[initial-value]
(into-array (useState initial-value)))
(defn use-atom-state [initial-value]
(let [[value set-value!] (use-state initial-value)]
(reify
IDeref
(-deref [o] value)
IReset
(-reset! [o new-value] (doto new-value set-value!))
ISwap
(-swap! [a f] (set-value! f))
(-swap! [a f x] (set-value! #(f % x)))
(-swap! [a f x y] (set-value! #(f % x y)))
(-swap! [a f x y more] (set-value! #(apply f % x y more))))))
I wonder if you think that's an interesting approach to use in Helix (can be add by the user as well, its a simple extension, but maybe would be cool to be part of the lib)
hey doing swell 🙂 hope you’re doing well
this enables things like:
(let [s (pvh/use-atom-state 0)]
(dom/button {:onClick #(swap! s inc)} (str "Counter " @s)))
I played with that idea in hx and ended up backing it out.
the main reason I decided not to use the atom interface was because setting React state is async, which is different behavior than an atom
interesting, wonder why, gonna read the issue 🙂
if I'm getting it right, were you trying to rely on the watch parts of the atom?
e.g. an event handler that did:
(fn handle-click []
(swap! s inc)
(fetch-some-data @s))
would be surprising to people who are familiar with atoms, since it would call fetch-some-data
with the old state value instead of the new onesince react applies state updates on the next render
the way I'm doing it would happen on the same loop (sync), not reallying (or supporting) the watch parts
I touhgh a bit about that, for example, if a swap! happens, should the value change? and I decided to go with No, this way it stays immutable as it passes, only updating by react itself on the next loop (as consequence of calling the state setter)
yeah I guess that issue is talking more about actually trying to use a sync atom
but the reason I backed it out was because it was confusing to people who thought it was like any other atom
yeah, its just that, the atom is such a better block to pass around 😛
I imagine some abstractions that can rely on that to make things flexible
eh, I’m not so sure.
there is more flexibility in splitting reading state from changing state
agents are async, no?
agents are, yes, and I thought about creating a send
-like API
i have no issue with the tuple, FWIW
but then I was like, returning a tuple seems just as good ¯\(ツ)/¯
I did add a bit of sugar on top to helix’s use-state
hook, so that the setter can be passed a function and a spread of arguments just like swap!
it’s a hard line to draw, how Clojure-y should a thin wrapper around React be. I think helix has made a lot of good decisions along the way
yeah, to be honest I'm doing all of this with Fulcro now, but I find Helix interesting too, may use on some projects
would love to get your feedback on it 🙂 I’ve been spending a lot of time in the fulcro docs recently, might be time to take it for a spin soon
(ns app.core
(:require
[helix.core :as hx :refer [$ <> defnc]]
[helix.dom :as d]
[helix.hooks :as hooks]))
(defnc App []
(hooks/use-effect :once (js/console.log "passed here"))
(d/div "Hello"))
(defn ^:export start
[]
(rdom/render ($ App) (js/document.getElementById "app")))
are you using the latest version of helix?
lilactown/helix {:git/url "<https://github.com/Lokeh/helix.git>"
:sha "376a68da794c6051f6fe103a79fec7314df67eb7"}
used from https://github.com/Lokeh/helix-todo-mvc
that’s quite old. try updating to the latest
I´ll try with new one
that was the problem
😞
thanks again
hooray!
if you want to create a PR to update the example to use the latest version so it’s less confusing for the next person, that would be appreciated
otherwise I’ll update it soon
sorry about my newbe knowleadge, what´s PR?
but anyway, I´d like to contribute yes ...
pull request
got it, sure
done