Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using external react components #20

Closed
abhishiv opened this issue Feb 15, 2015 · 14 comments
Closed

Using external react components #20

abhishiv opened this issue Feb 15, 2015 · 14 comments

Comments

@abhishiv
Copy link

This might be be just something missing from documentation, but I'm having a small issue getting external components to work. I'm using that like this:

[(.-Route js/ReactRouter) {:name "home"} ]

where window.ReactRouter.Route is the component I want to use. It throws an error from this function.

(defn normalize-element
  "Ensure an element vector is of the form [tag-name attrs content]."
  [[tag & content]]
  (when (not (or (keyword? tag) (symbol? tag) (string? tag)))
    (throw (ex-info (str tag " is not a valid element name.") {:tag tag :content content})))
  (let [[tag id class] (match-tag tag)
        tag-attrs (compact-map {:id id :class class})
        map-attrs (first content)]
    (if (map? map-attrs)
      [tag (merge-with-class tag-attrs map-attrs) (next content)]
      [tag tag-attrs content])))

Any pointers on how to get non rum components to work?

@tonsky
Copy link
Owner

tonsky commented Feb 15, 2015

When you write [(.-Route js/ReactRouter) {:name "home"} ] sablono tries to parse it as tag, something of form [:div attrs & children].

Just create component with interop syntax, e.g. (js/ReactRouter.Route #js {:name "home"}). Do not put in a vector as first element (but as child — in sablono terms — it’s ok)

@tonsky
Copy link
Owner

tonsky commented Feb 17, 2015

@abhishiv have you solved this?

@cldwalker
Copy link

I had no problem using a react-treeview component with rum. May be worth mentioning that rum is compatible w/ react components. I know that quiescent does

@taylorSando
Copy link

Is there an easy way to compose external react components, like say js/ReactBootstrap.ModalDialog?

I've only been able to create external react components with a (js/React.createElement js/ReactBootstrap.Button (clj->js args) content) command, which is not composable. I know in Reagent, you can just say [ReactBootstrap.Button args "label"]

@sabbaticaldev
Copy link

I think this could be clear in docs. Reagent has a function to adapt class, and I saw something similar in a cljs-material-ui wrapper, "adapt-rum-class" (https://github.com/madvas/cljs-react-material-ui/blob/f66e8047b556e41dce45b1391cae9ca13f25242f/src/cljs_react_material_ui/core.clj). Is it needed?

@Odie
Copy link

Odie commented Feb 25, 2017

Ran into this today...
extracted the relevant parts from cljs-react-material-ui and it worked like a charm.

(ns app.core
  (:require [sablono.util]))

(defn adapt-class [react-class]
  (fn [& args]
    (let [[opts children] (if (map? (first args))
                            [(first args) (rest args)]
                            [{} args])
          type# (first children)]
      (let [new-children (if (vector? type#)
                           [(sablono.interpreter/interpret children)]
                           children)]

        (apply js/React.createElement react-class
               (clj->js (sablono.util/html-to-dom-attrs opts)) new-children)))))

Would really appreciate it if an utility like this can be added to rum. It seems like a natural thing to want to do.

@priornix
Copy link
Contributor

priornix commented Jul 4, 2017

Thanks for this, I adapted this for use within my Antizer library, but also added a few things:

  • Changed (vector?) to (sequential?) to handle cases where child elements can be a list, eg: from a (for)
  • Check properties hash-map values for any valid HTML element tags and convert them to a React element
  • Sablono's html-to-dom-attrs function does not work for nested hash-maps, so I replaced it with a function I wrote map-keys->camel-case.
(require '[clojure.string :as string])
(require '[clojure.walk :as w])

(defn kebab-case->camel-case
  "Converts from kebab case to camel case, eg: on-click to onClick"
  [input]
  (let [words (string/split input #"-")
        capitalize (->> (rest words)
                        (map #(apply str (string/upper-case (first %)) (rest %))))]
    (apply str (first words) capitalize)))

(defn map-keys->camel-case 
  "Stringifys all the keys of a cljs hashmap and converts them
   from kebab case to camel case. If :html-props option is specified, 
   then rename the html properties values to their dom equivalent
   before conversion"
  [data & {:keys [html-props]}]
  (let [convert-to-camel (fn [[key value]]
                           [(kebab-case->camel-case (name key)) value])]
    (w/postwalk (fn [x]
                  (if (map? x)
                    (let [new-map (if html-props
                                    (rename-keys x {:class :className :for :htmlFor})
                                    x)]
                      (into {} (map convert-to-camel new-map)))
                    x))
      data)))

(defn adapt-class [react-class & args]
  (let [[opts children] (if (map? (first args))
                          [(first args) (rest args)]
                          [{} args])
        type# (first children)
        ;; we have to make sure to check if the children is sequential 
        ;; as a list can be returned, eg: from a (for)
        new-children (if (sequential? type#)
                       [(sablono.interpreter/interpret children)]
                       children)
        ;; convert any options key value to a react element, if
        ;; a valid html element tag is used, using sablono
        vector->react-elems (fn [[key val]]
                              (if (sequential? val)
                                [key (sablono.interpreter/interpret val)]
                                [key val]))
        new-options (into {} (map vector->react-elems opts))]
    (apply js/React.createElement react-class
      ;; sablono html-to-dom-attrs does not work for nested hash-maps
      (clj->js (map-keys->camel-case new-options :html-props true)) 
      new-children)))

@sooheon
Copy link

sooheon commented Nov 30, 2017

@priornix 's solution above has the problem where any data prop you pass in to the component, if it's in the form of a hash-map, will get keys mangled.

@roman01la
Copy link
Collaborator

We'll probably introduce something like this. @piranha @martinklepsch Do you have anything like that in your code?

@piranha
Copy link
Collaborator

piranha commented Apr 14, 2020

We don't have a lot of external React components and for those we have integration is just some custom code. We used antizer in Suppliers UI though and I didn't touch that for a while, but I think it worked well. :) And we have more external deps there.

@roman01la
Copy link
Collaborator

Actually, since we are going to fork Sablono I'm more into introducing [:> js/Component props & children] syntax, similar to Reagent

@piranha
Copy link
Collaborator

piranha commented Apr 15, 2020

Maybe... I'm not a fan of that cryptic stuff (hard to search for, hard to understand if you're seeing it for the first time). I'd prefer something like (def Comp (rum/pour js/Component)) (adapt-class I mean :)).

@roman01la
Copy link
Collaborator

I'm perhaps biased since I'm using Reagent now and it feels good to be able to use JS components inline, w/o making another def using class adapter. But I'm also fine with going both ways.

@roman01la
Copy link
Collaborator

Fixed in 3938ac1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants