Skip to content

Instantly share code, notes, and snippets.

@p-himik
Last active January 11, 2021 11:40
Show Gist options
  • Save p-himik/4c690f831ed3436b22572097896abb30 to your computer and use it in GitHub Desktop.
Save p-himik/4c690f831ed3436b22572097896abb30 to your computer and use it in GitHub Desktop.
Material-UI TextField with Reagent
(ns text-field
(:require
[goog.object :as gobj]
[reagent.core]
;; Note that this particular `:require` vector is for shadow-cljs.
;; Other build tools might use different conventions.
["@material-ui/core/TextField" :default TextField]))
;; Copied from `re-com.util`.
(defn deref-or-value
"Takes a value or an atom.
If it's a value, returns it.
If it's a Reagent object that supports IDeref, returns the value inside it by `deref`ing."
[val-or-atom]
(if (satisfies? IDeref val-or-atom)
@val-or-atom
val-or-atom))
(defn shallow-js->clj-props [props]
(into {}
(map (fn [k]
[(keyword k) (gobj/get props k)]))
(js-keys props)))
(def -text-field (reagent.core/adapt-react-class TextField))
;; All the keys are there just as documentation.
(defn text-field [{:keys [auto-complete auto-focus classes default-value disabled
error FormHelperTextProps full-width helper-text id
InputLabelProps InputProps input-props input-ref label
margin multiline name on-change placeholder required
rows rows-max select SelectProps type value variant]}]
(let [external-model (reagent.core/atom (deref-or-value value))
;; Need a default non-nil value to avoid React error
;; about switching from uncontrolled to controlled input.
internal-model (reagent.core/atom (if (nil? @external-model) "" @external-model))]
(fn [props]
(let [;; Deep `js->clj` cannot be used here because it will make all refs invalid.
;; The reason is that refs are expected to be mutable objects and a
;; `js->clj` -> `clj->js` round-trip creates a completely new object.
props (cond-> props (object? props) shallow-js->clj-props)
latest-ext-model (deref-or-value (:value props))
on-change (:on-change props)]
(when (not= @external-model latest-ext-model)
(reset! external-model latest-ext-model)
(reset! internal-model latest-ext-model))
[-text-field
(assoc props
:value @internal-model
:on-change (fn [evt]
(let [value (oget evt :target.value)]
(reset! internal-model value)
;; The flush is needed to prevent cursor jumping when editing the value
;; in the middle. Not sure why the flush is preventing it though.
;; Reagent repo has a different example but it delves into some
;; internals of Material UI and I don't really like that approach:
;; https://github.com/reagent-project/reagent/blob/master/doc/examples/material-ui.md
;; Some big discussion on it here:
;; https://github.com/madvas/cljs-react-material-ui/issues/17
(reagent.core/flush)
(when on-change
(on-change value)))))]))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment