There should be no difference between (hx.hooks/useEffect (fn [] .. ) [foo]) and (helix.hooks/use-effect [foo] (fn [] .. ) right? Other than the arg order change and support for :auto-dep :once and :always?
That's correct. Are you seeing a difference?
Yes, and I’m not sure why
I put in some debug console.logs and saw some behavior where auto-deps was running on every render and the one depending on a single binding was never running
Oh wait
Helix's use-effect expects you to write the body without a wrapping fn
gotcha, let me give it a shot
that was it
thanks for your help
Sure thing
I'm honestly contemplating removing a lot of the sugar for hooks like use-effect
Not needing to wrap it in a fn is nice, but it's surprising given that no 3rd party hooks work that way, and I can't extend the behavior to them
We were often doing (hx.hooks/useEffect (fn-that-returns-a-0-arity-fn) [deps])
hm interesting
Is there more sugar to be removed than the implicit fn?
I’ve also been thinking of deprecating :auto-deps
in favor of something better, but that’s a long ways off
in practice I haven’t actually found :auto-deps
that useful, although there are certain cases where I want that behavior
my future plan is to introduce a defhook
form which allows you to annotate arguments that are dependencies:
(defhook use-thing
[^:deps deps f]
,,, do things with deps vector and f)
which would allow defnc
to detect and warn if you haven’t filled in the deps vector completely:
(use-thing
[]
#(foo bar)) ;; => Warning! Deps array missing `foo` and `bar`
(use-thing
^{:deps/ignore [foo]} [bar]
#(foo bar)) ;; => Fine
and use a macro to auto-fill them for you:
(auto-deps
(use-thing
#(foo bar baz ... xyz)))
I don't quite see the need for :auto-deps or auto-deps. To me it seems quite a bit of mental overhead for very little gain.
It's important to quickly understand when a given effect is run by looking at the explicit deps. With auto-deps I'd have to carefully read the whole fn to understand when it's run.
I agree completely re: use-effect. the case I do see it as helpful is with optimizations like
(defnc my-component
[{:keys [prop1 prop2 ,,, propN]}]
(let [{:keys [context1 context2 ,,, contextN]} (use-context some-global)]
;; here we wrap the React Elements in use-memo to bail out of
;; rendering if the pieces of context we're using haven't changed
(use-memo
:auto-deps
(d/div ,,,))))
That's a good point.
my dream is to boil that down to
^:memo (d/div ,,,)
but I agree that there’s a semantic difference between things that do side-effects, which I really want to be explicit, and things that optimize, like use-memo
and use-callback
finding a way to easily inspect and troubleshoot the forms that the syntax sugar emits is also something I’m thinking about
I have written quite a bit of reagent and rum, and have only experimented with hx/helix (and hooks in general) so far. Maybe the biggest surprise for me was how often I need to use memos to make sure I don't just dumbly re-render too much. I don't buy the React idea that virtual rendering is so cheap that I don't have to think about it. It surely matters when there's a large list on the screen, for example. Also I don't want to use my users' precious mobile battery to re-render the same stuff all over again that they already have on their screen. So I like your idea of making these optimizations as straightforward as possible.
yep, I agree with you. performance is critical. reagent/rum and other libs that coordinate state outside of the component tree have a leg up w.r.t. performance in certain conditions. but my belief is that Concurrent Mode will be a big enough win in general that it will make it so that we only want (or need) to use those external state containers in very specific circumstances
factory functions should be similar in performance to hx. is that correct?
Yeah, I see it the same way. Have you published any (experimental) examples of data fetching with Suspense in helix?
I haven’t published anything yet, but I have been working on an example hacker news reader that uses suspense for data fetching
hoping I can use it to show some of the cool new features React has in it’s experimental branch, but there are some rough edges
yes
mainly around the rest of the ecosystem, tbh. things like react-router and react-spring don’t operate as well as one would hope in Concurrent Mode. but I think a lot of that will be ameliorate as adoption rises
Thank you
Okay! Sounds interesting. I hope you'll decide to publish it sometime soon. 🙂
Is there a specific reason to use react-router instead of something that's implemented in cljs on top of helix?
I thought it would be easier than writing my own 😛
there’s a lot that goes into routing (managing history, etc.) that I didn’t want to have to write
Did you consider secretary, bidi or reitit? They are easy to integrate to a reagent project, but would there be problems with helix (because of hooks)?
I have briefly started integrating reitit after running into issues with react-router, but I think it’s going to run into the same problems as react-router if I do it naively
the problem in concurrent mode is that state changes that exist outside of the component tree (like, say, the window’s path) doesn’t play as nicely with things like Suspense
Ah, I see.
like ideally, when a user clicks on a link to go to another view that needs some data, Suspense would intelligently determine whether to show interstitial states • if the data is already there, transition immediately • if the data isn’t there yet, but resolves very quickly, wait until it resolves before transition to the view and show local loading spinner on the link/button • if the data isn’t there yet, but is taking awhile, transition to the view and suspend with a full-page loading animation until resolved
this is what the useTransition
hook is for managing
React will do all that for you if you use use-state
, but if you are changing the state synchronously (by updating the window URL, for instance) and then reading that state from outside the tree to dispatch the view change, then React can’t do it’s smartness
If one would addEventListener('popstate', ...) in useEffect, and change the state from there, would it then play nicely with Suspense?
AFAIU in this case no, in order to get all the nice behavior you need to fire the set-state call from inside the useTransition
callback
i might not understand it well enough yet tho
Okay. I guess the Concurrent Mode is quite a can of worms.
Do you know if they use it in production in Facebook? Any info available how they have solved routing there?
they do use it in prod AFAIK
I’m not sure
I think what might work is managing the router state inside of React, and pushing/popping the history state as a side effect
but that might be a whole nother can of worms
Thank you for a very interesting conversation. Gotta go help my child now. I really appreciate your work and help, and hope to write great UIs with helix soon. With or without Concurrent Mode. 🙂
cheers! best of luck 😄