helix

https://github.com/Lokeh/helix
fabrao 2020-04-01T02:57:53.031Z

how do I use use-effects* to handle re-size screen?

fabrao 2020-04-01T03:11:55.031700Z

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}))))

fabrao 2020-04-01T03:12:35.032200Z

set-state came from

let [[state set-state] (hooks/use-state {:height js/window.innerHeight
                                            :pagina :login})]

lilactown 2020-04-01T03:38:41.032700Z

@fabrao check out the helix docs https://github.com/Lokeh/helix/blob/master/docs/hooks.md#doing-side-effects

lilactown 2020-04-01T03:41:32.034100Z

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

fabrao 2020-04-01T03:58:45.036100Z

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?

lilactown 2020-04-01T03:59:39.036600Z

they are dependencies, which will tell use-effect to run when foo or bar change

fabrao 2020-04-01T04:00:18.037700Z

where they came from?

lilactown 2020-04-01T04:00:25.037900Z

i made them up

fabrao 2020-04-01T04:01:20.039100Z

in case of resize, just use

(use-effect
  :always
  (set-state assoc :height js/window.innerHeight))
?

lilactown 2020-04-01T04:01:47.039400Z

that will run every single time your component renders

lilactown 2020-04-01T04:02:13.040200Z

so it will render, set the state, which will trigger a render, which will set the state, which will trigger a render….

lilactown 2020-04-01T04:02:23.040600Z

you might end up in an infinite loop

fabrao 2020-04-01T04:02:34.040900Z

so, in this case of "resize" event, how to handle this?

fabrao 2020-04-01T04:03:00.041300Z

instead of using (js/window.addEventListener "resize" #(resize {:set-state set-state}))) ?

lilactown 2020-04-01T04:03:10.041500Z

you’ll want to do something like that

lilactown 2020-04-01T04:03:22.041900Z

use-effect is what will setup and tear down the subscription

fabrao 2020-04-01T04:05:05.044100Z

see in that way, I think my code is correct

lilactown 2020-04-01T04:05:41.044600Z

I don’t know what your code looks like right now, but the code you pasted using use-effect* was not correct

lilactown 2020-04-01T04:05:56.045200Z

it will also run every render, so every time your component renders it will add a new event listener

fabrao 2020-04-01T04:06:00.045400Z

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}))) [])

fabrao 2020-04-01T04:06:05.045600Z

why not?

lilactown 2020-04-01T04:06:27.045900Z

ah see you changed it 🙂

lilactown 2020-04-01T04:07:02.046300Z

couple things you still need to do

lilactown 2020-04-01T04:07:24.047Z

removeEventListener has to be given the same exact function that you added

fabrao 2020-04-01T04:07:51.047800Z

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 too

lilactown 2020-04-01T04:08:18.048100Z

yeah I think that blog post is wrong

lilactown 2020-04-01T04:08:33.048400Z

I just picked the first one from google that looked sort of correct 🙂 my bad

lilactown 2020-04-01T04:08:51.048800Z

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

fabrao 2020-04-01T04:09:40.049800Z

that´s the only way to pass state to function

lilactown 2020-04-01T04:10:00.050200Z

it also needs to return a function that removes the event listener. the code you pasted will add and then immediately remove the listener

lilactown 2020-04-01T04:10:41.050900Z

you need to store the function you pass to addEventListener so that removeEventListener uses the same function

fabrao 2020-04-01T04:11:03.051300Z

oh, I see

fabrao 2020-04-01T04:11:45.052600Z

the problem is having set-state

lilactown 2020-04-01T04:11:50.052700Z

(hooks/use-effect
  :once
  (let [resize-handler ,,,]
    (js/window.addEventListener "resize" resize-handler)
    #(js/window.removeEventListener "resize" resize-handler)))

fabrao 2020-04-01T04:12:21.053400Z

hum, now I see the way to do this

fabrao 2020-04-01T04:13:06.053600Z

thank you so much

fabrao 2020-04-01T04:13:17.053900Z

for helping me

lilactown 2020-04-01T04:13:25.054100Z

sure thing 😄

fabrao 2020-04-01T04:13:46.054400Z

why are you create this helix?

fabrao 2020-04-01T04:14:01.054700Z

to hire people from javascript?

fabrao 2020-04-01T04:14:04.054900Z

🙂

fabrao 2020-04-01T04:15:02.055600Z

if not, that´s I´m doing migrating from reagent

lilactown 2020-04-01T04:21:01.056100Z

I wanted to use React Hooks and 3rd party React components easier

lilactown 2020-04-01T04:21:21.056600Z

and have a library that would work with future React features by default

lilactown 2020-04-01T04:21:34.056900Z

I use reagent at work, we are slowly adopting helix

2020-04-01T15:36:43.069800Z

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.

lilactown 2020-04-01T15:50:07.082100Z

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))

2020-04-01T16:07:54.082900Z

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.

lilactown 2020-04-01T16:18:31.083100Z

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

2020-04-01T18:19:31.083300Z

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.

lilactown 2020-04-01T18:40:59.083500Z

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?

2020-04-01T19:36:07.083700Z

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.

lilactown 2020-04-01T04:22:01.057500Z

a lot of our developers working on the front-end are JS developers too so

lilactown 2020-04-01T04:22:06.057800Z

it all sort of came together 🙂

fabrao 2020-04-01T12:25:58.060600Z

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

fabrao 2020-04-01T12:44:37.062800Z

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

fabrao 2020-04-01T12:55:09.063700Z

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 listener

fabrao 2020-04-01T12:55:35.064200Z

only one listener

lilactown 2020-04-01T14:30:18.064700Z

if (use-effect ,,,) doesn’t work but (react/useEffect ,,,) does, that sounds like a bug

lilactown 2020-04-01T14:30:51.065200Z

if you can show me a minimal reproduction then I can try and see how to fix it

lilactown 2020-04-01T14:32:00.066500Z

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

wilkerlucio 2020-04-01T15:35:20.068300Z

@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))))))

wilkerlucio 2020-04-01T15:35:43.068800Z

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)

lilactown 2020-04-01T15:36:17.069400Z

hey doing swell 🙂 hope you’re doing well

2020-04-01T15:36:43.069800Z

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.

wilkerlucio 2020-04-01T15:36:49.070200Z

this enables things like:

(let [s (pvh/use-atom-state 0)]
          (dom/button {:onClick #(swap! s inc)} (str "Counter " @s)))

lilactown 2020-04-01T15:37:01.070400Z

I played with that idea in hx and ended up backing it out.

lilactown 2020-04-01T15:37:07.070700Z

https://github.com/Lokeh/hx/issues/41

lilactown 2020-04-01T15:38:11.071900Z

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

wilkerlucio 2020-04-01T15:38:13.072Z

interesting, wonder why, gonna read the issue 🙂

wilkerlucio 2020-04-01T15:39:41.073800Z

if I'm getting it right, were you trying to rely on the watch parts of the atom?

lilactown 2020-04-01T15:40:13.074100Z

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 one

lilactown 2020-04-01T15:40:35.074700Z

since react applies state updates on the next render

wilkerlucio 2020-04-01T15:40:54.075200Z

the way I'm doing it would happen on the same loop (sync), not reallying (or supporting) the watch parts

wilkerlucio 2020-04-01T15:41:40.076400Z

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)

lilactown 2020-04-01T15:41:58.076800Z

yeah I guess that issue is talking more about actually trying to use a sync atom

lilactown 2020-04-01T15:42:35.077300Z

but the reason I backed it out was because it was confusing to people who thought it was like any other atom

wilkerlucio 2020-04-01T15:43:05.077600Z

yeah, its just that, the atom is such a better block to pass around 😛

wilkerlucio 2020-04-01T15:43:18.078100Z

I imagine some abstractions that can rely on that to make things flexible

lilactown 2020-04-01T15:43:31.078400Z

eh, I’m not so sure.

lilactown 2020-04-01T15:43:47.078800Z

there is more flexibility in splitting reading state from changing state

Derek Passen 2020-04-01T15:44:06.079200Z

agents are async, no?

lilactown 2020-04-01T15:44:23.079600Z

agents are, yes, and I thought about creating a send-like API

Derek Passen 2020-04-01T15:44:35.080100Z

i have no issue with the tuple, FWIW

lilactown 2020-04-01T15:44:39.080200Z

but then I was like, returning a tuple seems just as good ¯\(ツ)

lilactown 2020-04-01T15:45:11.081200Z

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!

Derek Passen 2020-04-01T15:45:25.081500Z

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

wilkerlucio 2020-04-01T15:45:54.082Z

yeah, to be honest I'm doing all of this with Fulcro now, but I find Helix interesting too, may use on some projects

lilactown 2020-04-01T15:50:07.082100Z

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))

lilactown 2020-04-01T15:50:41.082800Z

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

2020-04-01T16:07:54.082900Z

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.

lilactown 2020-04-01T16:18:31.083100Z

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

2020-04-01T18:19:31.083300Z

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.

lilactown 2020-04-01T18:40:59.083500Z

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?

2020-04-01T19:36:07.083700Z

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.

fabrao 2020-04-01T19:38:50.084Z

(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")))

lilactown 2020-04-01T21:18:50.084400Z

are you using the latest version of helix?

fabrao 2020-04-01T22:30:35.084600Z

lilactown/helix {:git/url "<https://github.com/Lokeh/helix.git>"
                         :sha "376a68da794c6051f6fe103a79fec7314df67eb7"}

fabrao 2020-04-01T22:31:48.084800Z

used from https://github.com/Lokeh/helix-todo-mvc

lilactown 2020-04-01T22:32:02.085100Z

that’s quite old. try updating to the latest

fabrao 2020-04-01T22:34:12.085300Z

I´ll try with new one

fabrao 2020-04-01T22:39:56.085500Z

that was the problem

fabrao 2020-04-01T22:39:58.085700Z

😞

fabrao 2020-04-01T22:40:07.085900Z

thanks again

lilactown 2020-04-01T22:40:11.086100Z

hooray!

lilactown 2020-04-01T22:40:37.086300Z

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

lilactown 2020-04-01T22:40:45.086500Z

otherwise I’ll update it soon

fabrao 2020-04-01T22:43:00.086700Z

sorry about my newbe knowleadge, what´s PR?

fabrao 2020-04-01T22:43:58.086900Z

but anyway, I´d like to contribute yes ...

lilactown 2020-04-01T22:44:11.087100Z

pull request

fabrao 2020-04-01T22:47:23.087300Z

got it, sure

fabrao 2020-04-01T23:50:58.087500Z

done