Controlled TextField with Material-UI + Reagent

I’m using Material-UI + Reagent/Re-frame for a new project, and I’ve come across the problem of the controlled Material-UI TextField not working well with Reagent, as described in the Reagent docs.

I started using the suggested workaround, but wasn’t really happy about it not working with multiline text fields with a non-fixed number of rows.

Then it occurred to me that there’s an easier workaround, which is to just use an uncontrolled TextField with a ^{:key ...} metadata attached. When I need to re-render the TextField (e.g. after fetching data from a database) then I can change the key to force a re-render.

;; A way to force an uncontrolled textfield to re-render is to change the key
(defonce !text (r/atom "Initial text in !text atom"))
(defonce !key (r/atom 1))

(defn uncontrolled-with-key []
  [:form {:no-validate true
          :auto-complete "off"}
   ^{:key @!key}
   [:> TextField {:id "uncontrolled-textfield"
                  :multiline true
                  :variant "outlined"
                  :label "Uncontrolled"
                  :on-change #(reset! !text (-> % .-target .-value))
                  :default-value @!text}]])

(comment
  ; Run these snippets in the REPL

  ; Changing the text atom won't make the textfield re-render
  (reset! !text "Some new text")

  ; But changing the key will
  (swap! !key inc))

Is there any downside to doing things this way?

The key prop is sort of the only way to actually/manually control rendering apart from reaching for “full” class components, and I definitely use it to exert precise control when required, so I’d say go for it if it works for you!

React’s approach to rendering/UI is for components to be “pure functions from data/state to element trees”. By using a component’s key to control the render cycle, you’re somewhat going against this paradigm, which to me sounds perfectly fine except when you need to do it a lot, which would suggest either at least a weakness of the paradigm for your use case, or some smells in how data flow is integrated with your render logic.

If you need it “here and there” to have more manually controlled renders, it’s a good tool to use; if you find you’re using it pervasively to override how React “wants” to work, there are probably cleaner ways to achieve what you want. In the case of controlled components like for user input, I find those very often require custom rendering logic that just doesn’t fit into everything being cleanly derived.

Thanks, that makes sense! I can imagine things could get out of hand if I used the key to control render in many places in my code. My app is fairly straightforward so hopefully it will be OK.