Last active
January 11, 2021 11:40
-
-
Save p-himik/4c690f831ed3436b22572097896abb30 to your computer and use it in GitHub Desktop.
Material-UI TextField with Reagent
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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