Search code examples
clojurescriptom

How to create Material UI component in Om Clojurescript?


First of all, this https://github.com/taylorSando/om-material-ui doesn't work with latest React/Material UI. The main reason, I think, is this warning in console:

Warning: Something is calling a React component directly. Use a factory or JSX instead. See: https://fb.me/react-legacyfactory

I've also tried to create component "manually":

(ns om-test.core
  (:require [om.core :as om :include-macros true]
            [om-tools.dom :as dom :include-macros true]
            [om-tools.core :refer-macros [defcomponent]]
            [om-material-ui.core :as mui :include-macros true]))

(enable-console-print!)

(defonce app-state (atom {:text "Hello Chestnut!"}))

(defn main []
  (om/root
    (fn [app owner]
      (reify
        om/IRender
        (render [_]
          (dom/div (dom/element js/MaterialUI.Paper {} "Hello")
                   (mui/paper {} "Hello"))
          )))
    app-state
    {:target (. js/document (getElementById "app"))}))

So, both of these approaches produces same warning above.

There has been obviously some changes with React. It suggests to create components programatically as:

var React = require('react');
var MyComponent = React.createFactory(require('MyComponent'));

function render() {
  return MyComponent({ foo: 'bar' });
}

So how do I create Material UI component inside Om render function, or maybe better How do I create React component inside Om render function, in general?

By Material UI I mean this https://github.com/callemall/material-ui

My dependencies

:dependencies [[org.clojure/clojure "1.6.0"]
                 [org.clojure/clojurescript "0.0-3058" :scope "provided"]
                 [ring "1.3.2"]
                 [ring/ring-defaults "0.1.4"]
                 [compojure "1.3.2"]
                 [enlive "1.1.6"]
                 [org.omcljs/om "0.9.0"]
                 [environ "1.0.0"]
                 [http-kit "2.1.19"]
                 [prismatic/om-tools "0.3.11"]
                 [om-material-ui "0.1.1" :exclusions [org.clojure/clojurescript
                                                      org.clojure/clojure]]]

Solution

  • Okay I eventually figured out.

    1. Build latest version of Material UI with this: https://github.com/taylorSando/om-material-ui/tree/master/build-mui. Note: No need to build CSS in current version (0.10.4)
    2. Include built material.js into your HTML file. Again, no need to include CSS.
    3. Avoid loading React twice https://github.com/taylorSando/om-material-ui#avoid-loading-react-twice

    Now the code for Om:

    (ns material-ui-test.core
      (:require [om.core :as om :include-macros true]
                [om.dom :as dom :include-macros true]))
    
    (enable-console-print!)
    
    (defonce app-state (atom {:text "Hello Chestnut!"}))
    
    (def ^:dynamic *mui-theme*
      (.getCurrentTheme (js/MaterialUI.Styles.ThemeManager.)))
    
    (defn main []
      (om/root
        (fn [app owner]
          (reify
            om/IRender
            (render [_]
              (let [ctor (js/React.createFactory
                           (js/React.createClass
                             #js
                                 {:getDisplayName    (fn [] "muiroot-context")
                                  :childContextTypes #js {:muiTheme js/React.PropTypes.object}
                                  :getChildContext   (fn [] #js {:muiTheme *mui-theme*})
                                  :render            (fn []
                                                       (dom/div nil
                                                                (dom/h1 nil (:text app))
                                                                (js/React.createElement js/MaterialUI.Slider)))}))]
                (ctor. nil)))))
        app-state
        {:target (. js/document (getElementById "app"))}))
    

    If you used just (js/React.createElement js/MaterialUI.Slider) without :getChildContext etc. it would throw error:

    Uncaught TypeError: Cannot read property 'component' of undefined

    This is because of how current MaterialUI works. Read "Usage" part here: http://material-ui.com/#/customization/themes

    Code for Reagent is bit more elegant. But I've used here namespace

    [material-ui.core :as ui :include-macros true]

    copy-pasted from this example project: https://github.com/tuhlmann/reagent-material

    (def ^:dynamic *mui-theme*
      (.getCurrentTheme (js/MaterialUI.Styles.ThemeManager.)))
    
    (defn main-panel []
      (let [active-panel (rf/subscribe [:active-panel])]
        (r/create-class
          {:display-name "Main Panel"
    
           :child-context-types
                         #js {:muiTheme js/React.PropTypes.object}
    
           :get-child-context
                         (fn [this]
                           #js {:muiTheme *mui-theme*})
           :reagent-render
                         (fn []
                           [ui/Slider {:name "slide1"}])})))
    

    EDIT: I released library, which greatly simplifies whole process.

    Library: https://github.com/madvas/cljs-react-material-ui

    Example app: https://github.com/madvas/cljs-react-material-ui-example