lilactown / helix

A simple, easy to use library for React development in ClojureScript.
Eclipse Public License 2.0
624 stars 52 forks source link

Create use-isomorphic-layout-effect #143

Closed khmelevskii closed 1 year ago

khmelevskii commented 1 year ago

I'm trying to create use-isomorphic-layout-effect based on use-effect and use-layout-effect. Something like this

(def use-isomorphic-layout-effect
  (if (= (type js/window) "undefined")
    helix.hooks/use-effect
    helix.hooks/use-layout-effect))

But I can't do this because use-effect and use-layout-effect are macro

lilactown commented 1 year ago

You can use react/useEffect and react/useLayoutEffect to build it yourself. The downside is you won’t have the same syntax as use-effect and use-layout-effect.

You could build your own macro that used a def that was determined the way above. Example

(def use-isomorphic-layout-effect*
  (if (= (type js/window) "undefined")
    react/useEffect
    react/useLayoutEffect))

(defmacro use-isomorphic-layout-effect ,,,)
khmelevskii commented 1 year ago

Yes, I understand that I can do something like this. I prefer to have use-isomorphic-layout-effect with the same interface as use-effect.

Because of use-effect and use-layout-effect are macro, I can't create use-isomorphic-layout-effect based on them and I need to use react versions as you described above. But it means that I need to copy some of helix code https://github.com/lilactown/helix/blob/master/src/helix/hooks.cljc#L189.

I'm not sure that it's a good idea to add custom hook to helix and I'm also not sure that helix need to have additional hooks with the same interface but as functions(not macros). So, I think I will copy part of helix code to do my own macro.

If you don't have any other ideas for this case I can close ticket

rome-user commented 1 year ago

I think it is possible to write macro that invokes cljs.analyzer.api to grab compiler options and emit different macros depending on the active compilation target.

I will try to make a proof of concept soon.

rome-user commented 1 year ago

This seems to work well enough.

(ns demo.hooks
  #?(:clj  (:require [cljs.analyzer.api :as analyzer])
     :cljs (:require-macros [demo.hooks])))

#?(:clj
   (defmacro use-isomorphic-effect [& body]
     ;; Although shadow offers a variety of targets, the CLJS compiler itself
     ;; has roughly the following distinctions:
     ;;
     ;; "default"   - browser
     ;; "nodejs"    - node runtime
     ;; "webworker" - web worker
     ;;
     ;; We assume that a target of Node is code intending to run on a server.
     (let [effect-hook (if (= "nodejs"
                              (get-in (analyzer/get-options)
                                      [:closure-defines 'cljs.core/*target*]))
                         'helix.hooks/use-effect
                         'helix.hooks/use-layout-effect)]
       `(~effect-hook ~@body))))
khmelevskii commented 1 year ago

This is very useful. Thank you @rome-user !

lilactown commented 1 year ago

This seems to work well enough.

(ns demo.hooks
  #?(:clj  (:require [cljs.analyzer.api :as analyzer])
     :cljs (:require-macros [demo.hooks])))

#?(:clj
   (defmacro use-isomorphic-effect [& body]
     ;; Although shadow offers a variety of targets, the CLJS compiler itself
     ;; has roughly the following distinctions:
     ;;
     ;; "default"   - browser
     ;; "nodejs"    - node runtime
     ;; "webworker" - web worker
     ;;
     ;; We assume that a target of Node is code intending to run on a server.
     (let [effect-hook (if (= "nodejs"
                              (get-in (analyzer/get-options)
                                      [:closure-defines 'cljs.core/*target*]))
                         'helix.hooks/use-effect
                         'helix.hooks/use-layout-effect)]
       `(~effect-hook ~@body))))

Not sure if this works with all shadow-cljs targets that users may want to fallback to use-effect, but I think this is easily testable and manageable in peoples' projects for now. Thank you for this!