Fork me on GitHub
#reagent
<
2022-04-13
>
Alexis Schad08:04:40

Hi there, is there a pattern to do like a useEffect listening to a prop change? Simple exemple: I have a input type text with an internal state for the value. But I want it to be semi-controlled. So every time the props.value changed, I want to update my internal state. Like a useEffect(() => setValue(props.value), [props.value]) . A native cljs/reagent solution I found was to store the props.value in a ratom, and do a (when (not= @old-props-value props-value) (reset! old-props-value props-value) (do-some-stuff-here)) Thanks if you can help!

Lu09:04:34

Do you mind explaining a bit better what the end goal is?

Alexis Schad09:04:34

The same use cases as the useEffect hook in React, I've provided one above. Was my example not clear?

Lu09:04:43

It is! But what I'm trying to understand is that you want to track the value onChange but without using the onChange callback is that right?

Alexis Schad09:04:10

the value is in the props so there's no on change callback I literally want the equivalent of useEffect(() => setInternalValue(props.value), [props.value]) I may create myself an helper for these, or use React.useEffect directly, but I wanted to know if there's a native cljs/reagent solution for listening to a prop change

Lu09:04:48

I don’t think there’s a native reagent solution for that. I’d just go with:

(defn component []
  (r/useEffect (fn []
                 ;; your logic in here
                 identity))
  [:div
   [:input
    {:name "hello"}]
   [:div "other stuff"]])

[:f> component]

Lu09:04:04

The solution you found is not ideal because you’ll end up performing side effects in the render block.. If you don’t want to use the useEffect hook, you could simply give your input an on-change callback and it’ll remain uncontrolled as long as you don’t also give it a value attribute.. I’d personally go with this approach

Alexis Schad10:04:17

I don't want it to be uncontrolled. It needs a value to work. The real use case that I had is a code/edn editor. I give a edn structure as the value to my component. The component uses pprint and display it in a textarea. The on-change callback is called only when the user click on a button "apply changes". So there's an internal on-change loop when the user modify the edn, and then when the button is clicked it calls the props/on-change. I don't want to call the props/on-change each time the textarea area changes, because it performs a parse-string/pprint loop.

Alexis Schad10:04:38

Thanks for you help, just wanted to be sure there's nothing native for that

Alexis Schad10:04:21

(defn create-use-effect []
  (let [previous-args (atom ::nil)]
    (fn [fn args]
      (when (not= @previous-args args)
        (reset! previous-args args)
        (apply fn args)))))

(defn component []
  (let [use-effect (create-use-effect)
        internal-value (r/atom nil)]
    (fn [value]
      (use-effect #(reset! internal-value %) [value]))))

Alexis Schad10:04:01

Sometimes it is just easier to implement your own logic, I didn't tested it yet

lilactown15:04:33

doing side effects in render like you're doing may break in a future version of react

lilactown15:04:48

however, that is how we do it in a couple places (and we need to fix it) 😄

lilactown15:04:58

the "right way" to do it is to do useEffect like Lu showed above and render the component via [:f> component ,,,] so you can use hooks in it. or, to use a form 3 component and component-did-update

lilactown15:04:51

e.g.

(defn edn-editor
  [data on-change]
  (let [[text set-text] (react/useState
                          #(with-out-str (clojure.pprint/pprint data))]
    ;; TODO debounce on-change call
    (react/useEffect
     (fn []
       (on-change (edn/read-string text))
       js/undefined)
     #js [text])
    ;; update text state when `data` prop changes
    (react/useEffect
     (fn []
       (set-text
        (with-out-str
          (clojure.pprint/pprint data)))
      js/undefined)
     #js [data])
    [:textarea
     {:value text
      :on-change #(set-text (.. % -target -value))}]))

Alexis Schad16:04:17

Yes, thanks. I think that's near the limitations of reagent, it's not very complient with modern React. I discovered UIx and I think I will migrate to it when I have the time.

lilactown17:04:45

uix is pretty nice. there's also #helix 😄