Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@pesterhazy
Last active November 7, 2021 17:54
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pesterhazy/2a25c82db0519a28e415b40481f84554 to your computer and use it in GitHub Desktop.
Save pesterhazy/2a25c82db0519a28e415b40481f84554 to your computer and use it in GitHub Desktop.
ClojureScript: bare React with ES6 classes (extending React.Component, no createClass or reagent)
(ns demo.react-cljs-es6-classes
(:require [goog.object :as gobj]))
;; Demo of using bare React using ES6 classes (without createClass or reagent)
;;
;; Equivalent of Javascript/JSX:
;;
;; class MyComponent extends React.Component {
;; constructor(props) {
;; super(props);
;; this.state = {counter: 0};
;; }
;; _inc() {
;; this.setState((prevState) => ({count: prevState.count}));
;; }
;; render() {
;; return (
;; <div>
;; <div>Counter: {this.state.counter}</div>
;; <button onClick=_inc>Click me</button>
;; </div>
;; );
;; }
;; }
;;
;;
;; Uses React 16
;;
;; h/t Thomas Heller
;; https://gist.github.com/thheller/7f530b34de1c44589f4e0671e1ef7533#file-es6-class-cljs-L18
(enable-console-print!)
(defn make-component
([display-name m] (make-component display-name nil m))
([display-name construct m]
(let [cmp (fn [props context updater]
(cljs.core/this-as this
(js/React.Component.call this props context updater)
(when construct
(construct this))
this))]
(gobj/extend (.-prototype cmp) js/React.Component.prototype m)
(when display-name
(set! (.-displayName cmp) display-name)
(set! (.-cljs$lang$ctorStr cmp) display-name)
(set! (.-cljs$lang$ctorPrWriter cmp)
(fn [this writer opt]
(cljs.core/-write writer display-name))))
(set! (.-cljs$lang$type cmp) true)
(set! (.. cmp -prototype -constructor) cmp))))
(def create-element js/React.createElement)
(def my-component
(make-component "MyComponent"
(fn [this] (set! (.-state this) #js{:counter 0}))
#js{:render
(fn []
(this-as this
(create-element "div"
nil
#js[(create-element "div"
#js{:key 1}
#js["Counter is " (-> this .-state .-counter)])
(create-element "button"
#js{:key 2
:onClick #(.setState this
(fn [old]
#js{:counter (-> old .-counter inc)}))}
#js["Click me"])])))}))
(defn run []
(js/ReactDOM.render (create-element my-component) (js/document.getElementById "app")))
(run)
@invasionofsmallcubes
Copy link

I was looking for this everywhere

@martinklepsch
Copy link

Hello there 👋 Could this be used to generate classes like in this example of using Cloudflare Workers?

@pesterhazy
Copy link
Author

@martinklepsch probably but that example doesn't use inheritance so the complexity might not be necessary. Would something simpler work?

cljs.user=> (defn Foo [a] #js{:bar (fn [] (prn :bar a))})
#'cljs.user/Foo
cljs.user=> (def foo (Foo. 42))
#'cljs.user/foo
cljs.user=> (.bar foo)
:bar 42

@martinklepsch
Copy link

martinklepsch commented Nov 7, 2021

Ah thank you! I didn't realize you could just "fake" class like this! In the end I also had to access this which prove a bit more complicated with the plain defn approach but deftype is actually working really well here.

(deftype Counter [state env]
  Object

  (initialize [this] ,,,)

  (fetch [this request]
    (let [path (.-pathname (js/URL. (.-url request)))]
      (js/console.log "this-state" (.-state this)) ; state and env are available here
      (case path
        "/inc" (js/Response. "/inc called")
        "/dec" (js/Response. "/dec called")
        (js/Response. "Only /inc and /dec allowed")))))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment