Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dev/refresh_example.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
(defnc app
[]
{:helix/features {:fast-refresh true}}
(let [[o set-o] (hooks/use-state
(let [_ (hooks/use-callback :auto-deps #(apply str %1 %&))
[o set-o] (hooks/use-state
#js {:name "Lisa"})]
(d/div
{:style {:text-align "center"
Expand Down
19 changes: 14 additions & 5 deletions src/helix/impl/analyzer.clj
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,23 @@ Example: ($ %s %s ...)"
(map :name)
vec)))

(defn- normalise-jsval
[x]
(if (= JSValue (type x))
(.-val x)
x))

(defn- normalise-sym
[x]
(if (symbol? x)
(symbol (string/replace (str x) #"^((p\d+|rest))(__\d+)#$" "$1__#"))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC this string/replace takes a value like p123__456# and returns p123__#, and this works because the pX number is related to the place within the form. Did I get that right?

I am curious if the pX is local to the binding form, or to the file, or global. I could see any of them being reasonable. I think best for us would be local to the binding form, because it would mean edits to other places in the same file/project wouldn't bust the fast refresh cache. I think no matter what, this is better than what we had before!

Copy link
Contributor Author

@SevereOverfl0w SevereOverfl0w Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first number is the index of the parameter, yep. So % corresponds to p1, so does %1. %2 corresponds to p2 etc.

And obviously %& corresponds to rest.

So local to the function bindings.

x))

(defn- unwrap-js-vals
(defn- normalise-vals
[form]
(clojure.walk/prewalk
(fn unwrap [x]
(if (= JSValue (type x))
(.-val x)
x))
(-> x normalise-jsval normalise-sym))
form))


Expand All @@ -105,7 +114,7 @@ Example: ($ %s %s ...)"
(defn find-hooks
[body]
(let [f (fn f [matches form]
(let [form (unwrap-js-vals form)]
(let [form (normalise-vals form)]
(if (and (seqable? form)
(not
;; Ignore quoted forms, e.g. '(use-foo)
Expand Down
17 changes: 16 additions & 1 deletion test/helix/impl/analyzer_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,19 @@
'[(use-foo)] '(use-foo)
'[(use-foo)] '{:foo (use-foo)}
'[(use-foo)] '{:foo #{use-bar (use-foo)}}
'[] '('use-foo bar)))
'[] '('use-foo bar)
'[(use-callback (fn* [p1__#] (identity p1__#)))] '(use-callback (fn* [p1__20354#] (identity p1__20354#))))
(t/testing "normalises bindings"
(t/are [form1 form2]
(= (hana/find-hooks (list 'use-callback form1))
(hana/find-hooks (list 'use-callback form2)))
'(fn* [p1__20354#] (identity p1__20354#))
'(fn* [p1__20364#] (identity p1__20364#))

'(fn* [p1__20356# & rest__20357#] (identity p1__20356# rest__20357#))
'(fn* [p1__20366# & rest__20367#] (identity p1__20366# rest__20367#))

'(fn* [p1__20359# p2__20360# p3__20361# & rest__20362#]
(identity p1__20359# p2__20360# p3__20361# rest__20362#))
'(fn* [p1__20369# p2__20370# p3__20371# & rest__20372#]
(identity p1__20369# p2__20370# p3__20371# rest__20372#)))))