Fork me on GitHub
#reagent
<
2020-04-25
>
yenda16:04:59

Using hooks with reagent is a pain, using functional components gets you all kind of problems, for instance in props namespaced keywords and sets are not properly converted. Some hooks are completely useless like useState because a ratom replaces it easily, but others are unavoidable when working with some libraries. I figured that you can workaround the props issue by using a separate component that you add to your view with this syntax [:<> [myhook param1 param2] [rest-of-my-views]] Here is an example of this workaround for a rewrite of useScrollToTop from react-navigation, to handle an onPress event from a parent: clojure

(defn use-scroll-to-top-hook
  "Hook to scroll to top
  Takes an atom containing a ref to the flat list and scrolls to top
  when the view is focused and user presses on the home tab"
  [!ref]
  (let [use-scroll-to-top
        (fn []
          (let [^js navigation (useNavigation)
                ^js route (useRoute)]
            ;; useEffect takes a function that returns a function that will
            ;; be called onDestroy, in this case addListener is returning
            ;; a function to unsubscribe to the listener
            (useEffect
             (fn []
               (let [^js current
                     ;; we recursively loop through navigation parent until
                     ;; we get to the navigator that contains the home tab
                     ;; which emits the tabPress event we are interested in
                     ;; this is basically what the useScrollToTop hook from
                     ;; react-navigation does except we don't stop at the first
                     ;; parent
                     (loop [^js current navigation]
                       (if-not (and current
                                    (= "tab" (.-type ^js (.dangerouslyGetState current)))
                                    (= "home" (first ^js (.-routeNames ^js (.dangerouslyGetState current)))))
                         (recur (.dangerouslyGetParent current))
                         current))]
                 (.addListener
                  current
                  "tabPress"
                  (fn [event]
                    (when (.isFocused navigation)
                      (js/requestAnimationFrame
                       (fn []
                         (when-let [^js ref @!ref]
                           (.scrollToOffset ref (clj->js {:offset 0
                                                          :animated true}))))))))))))
          nil)]
    [:> use-scroll-to-top]))

crinklywrappr17:04:36

Can someone help me with externs? I wrote this code to improve on the datatables recipe: https://pastebin.com/PxPinWKw. After setting infer-externs & `warn-on-infer` I get these warnings: https://pastebin.com/nwdFJSZQ. My understanding is that I'm supposed to provide type hints, but I don't know how to determine the types I'm supposed to hint eg "what is .destroy a member function of?"

aisamu18:04:58

IIRC The type hint can just say "this is a js object"/`^js`, and the names won't be munged

crinklywrappr18:04:44

I added (defn refs [^js/React.Component component] (.-refs component)) and it looks like that removed one of my warnings. Now I need to do the same thing for .-main. Confession: I don't know what refs is or how to divine the type. Is there anything like (type thing) which actually gives me useful information in cljs? Because type isn't giving me what I want.

aisamu18:04:43

Does a simple ^js (i.e. without the /React.Component qualifier) work?

crinklywrappr18:04:55

I get more warnings with that, so I'm guessing no.

Alex18:04:01

Has anyone used antizer the wrapper around Ant.Design? I'm trying to look at an example of creating an editable table.

crinklywrappr18:04:19

To try this a third way, the externs guide (https://clojurescript.org/guides/externs) teaches you to type hint in order to produce externs. @dnolen goes through an example where he type hints wrap-baz with ^js/Foo.Bar, but doesn't show me how he determined that ^js/Foo.Bar is the type hint which was needed. How did he do that?

Patrick Truong19:04:07

Hello everyone, I’m new to ClojureScript and asked this question in the beginners channel, to which someone recommended me maybe asking this here: I’m working on a basic Reagent project using just shadow-cljs. I’m using cider nrepl and VS Code Calva. On my main app file, I have a simple atom for toggle:

(ns alpha-journal.core
  (:require
   [reagent.core :as r]
   [reagent.dom :as dom]))

(def toggled (r/atom false))

(defn app []
  [:div {:class [(when true 'bg-red-500) 'h-screen]}
  ...
which is referenced in the app component. However, when I used my repl and lookup any variables or functions, it gives me the value, but also gives me a warning:
alpha-journal.core=> @toggled
------ WARNING - :undeclared-var -----------------------------------------------
 Resource: :1:2
 Use of undeclared Var alpha-journal.core/toggled
--------------------------------------------------------------------------------
false
What does this warning mean? Why are my variables considered undeclared? How can I get fix this warning? I saw on the ClojureScript reference (https://clojurescript.org/reference/repl-options#warn-on-undeclared) that I can set a compiler option to :warn-on-undeclared false but I’m not sure why I would need to do that in the first place. Thanks for all the help 🙂

Andres Moreno04:05:52

@U05224H0W Do you have any hints on this one? I am new to all of this (CLJS, shadow-cljs) and have the same question. Thanks.

thheller07:05:14

I don't know. I suspect wrong REPL use

pcj19:04:06

Here's a macro I quickly wrote to handle hooks and hocs. Tested it on @material-ui/styles withStyles and makeStyles

(defn ^:private extract-hooks
  "Provides hooksv and hooksjs to the defc macro."
  [opts]
  (reduce-kv
   (fn [m k v]
     (let [sym (gensym 'hook)]
       (-> m
           (update :hooksv conj sym)
           (update :hooksv conj (list v))
           (assoc-in [:hooksjs k] sym))))
   {:hooksv []
    :hooksjs {}}
   (:hooks opts)))


(defn ^:private extract-hocs
  "Provides hocs to the defc macro."
  [opts]
  (map list (:hocs opts)))


(defmacro defc
  [name opts args & body]
  (let [opts (if (string? opts) {:doc opts} opts)
        {:keys [hooksjs hooksv]} (extract-hooks opts)
        hocs (extract-hocs opts)
        inner-sym1 (gensym 'inner)
        inner-sym2 (gensym 'inner)
        inner-sym3 (gensym 'inner)
        opts (-> opts
                 (dissoc :hooks)
                 (dissoc :hocs))]
    `(do

       (defn- ~inner-sym3 ~args ~@body)

       (def ^{:private true}
         ~inner-sym2
         (-> (fn [args#]
               (into [~inner-sym3 (cljs.core/js->clj (dissoc args# :children) :keywordize-keys true)]
                     (:children args#)))
             (reagent.core/reactify-component)
             ~@hocs))

       (defn- ~inner-sym1
         [args#]
         (let ~hooksv
           (reagent.core/create-element
            ~inner-sym2
            (cljs.core/clj->js (merge ~hooksjs (goog.object/get args# "props")))
            (goog.object/get args# "children"))))

       (defn ~name
         [props# & children#]
         (let [has-props?# (map? props#)
               _children# (if has-props?# children# (conj children# props#))
               _props# (if has-props?# props# {})]
           (reagent.core/create-element
            ~inner-sym1
            (cljs.core/js-obj "props" _props# "children" _children#)))))))
Can be used like so:
(defc ctest
  {:hocs [(withStyles #js{:root #js{:color "green"}})]}
  [props & children]
  (into [:div {:class (-> props :classes :root)}] children))

(defn some-component []
  [ctest [:p "Text is green"]])

👏 4