This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-08-15
Channels
- # announcements (9)
- # babashka (1)
- # beginners (1)
- # calva (7)
- # cider (13)
- # clj-kondo (2)
- # cljsrn (1)
- # clojure (40)
- # clojure-europe (2)
- # clojure-spec (9)
- # clojure-uk (1)
- # cursive (4)
- # datomic (2)
- # etaoin (1)
- # fulcro (4)
- # honeysql (3)
- # lsp (43)
- # malli (7)
- # music (1)
- # nbb (6)
- # off-topic (5)
- # polylith (8)
- # protojure (1)
- # re-frame (6)
- # react (17)
- # reagent (63)
- # releases (1)
- # shadow-cljs (8)
- # testing (8)
- # tools-deps (1)
- # vim (8)
Hey there, I'm new to both React and reagent and I'm looking for a way to prevent a component from re-rendering while it is in focus. What is the recommended way to do something like that?
It should be possible to specify this function using reagent.core/create-class
: https://reactjs.org/docs/react-component.html#shouldcomponentupdate
So it would always return false
when something in the component is in focus.
But TBH, what you're describing something rather suspicious. My intuition is that it would be better to track "component is focused" as a part of your app's state and make explicit decision at the component's parent level whether to send in the new props or not, or something like that.
Initially, I wanted to do that, but I couldn't get it to work. The function was simply never called. Then, I read that this should only be used for performance optimisations, so I thought I'd ask elsewhere first to see if there's a better way and before I waste my time on this.
Hmm, trying to do this via the parent seems like a good idea. I'm not sure if this'll work in my case, but I can try
For context, what I have is essentially an editor for some structured data, and multiple components that represent different ways to edit it (and they should all be synced with the data). However I don't want to update the text editor component while a user is typing because that leads to weird trip-ups
Keep the internal state in an internal ratom, update the external one once the component loses focus or a user presses Enter.
Also, thinking about using props and deciding it in the parent, it would still be re-rendered either way, since I can't just say "use the component you had before", right?
Oh, so you mean that if you enter something in a text area, you don't want that very same text area to receive the newly entered data because it messes with up the text cursor?
Right, yeah, and this editor component also auto-formats which doesn't really work live
Alright. What I describe specifically is a problem of text input components in Reagent. Or, rather, in React in general if the component is a controlled one. Some libraries fix it, some don't. Reagent fixes it for the regular HTML inputs, but e.g. for a Material-UI text input I had to fix it myself by IIRC wrapping an uncontrolled component in a thin controlled wrapper. But I think you describe a different scenario. I think having two sets of ratoms would still solve it - you would dereference either the internal ratom or the external one, depending on whether the component is focused or not.
You can organize that with an appropriate ratom separation. I'm afraid your problem description is still too vague for me to write something more specific.
Well I had been thinking about each editor component having its own view of the data but I thought this would be really bad in terms of design because of redundancy and consistency. Also as soon as you introduce more ways to edit the data you run into the mediator problem
I'm sorry if I'm being vague, as I said I'm really new to this. I've never done proper frontend development before
This is the renderer for the component in question, if it helps at all. commands
is the shared data
(fn [format]
[:> ace-editor
{:mode (ace-mode format)
:width ""
:height ""
:class "min-h-full rounded border-2 border-gray-400 border-b-1 w-full"
:value (con/write-str format @commands)
:on-change (fn [text _] (reset! commands (con/read-str format text)))
:on-blur (fn [_ _] (reset! focused false))
:on-focus (fn [_] (reset! focused true))}])
That's alright. If you still think that there's something good in that idea, you should come up with the simplest example that would demonstrate it, and then write out the desired scenario. Something like "there are two text inputs on a page; the user types a sentence in the first field - the second field gets updated with the result of a function called on the contents of the first input; user types something in the second input field - something else happens". I'm afraid your code doesn't help me at all because I still have no idea what the usage scenario is. What the, possibly imaginary, state machine is - what the states are, what the transitions are.
All editor components share the same data, they just display it differently and allow you to edit it differently. To the left there is a visual representation of the data that can be edited through a GUI. To the right there is the textual representation of the data which can be edited like in a text editor. When I add or change something in either, the other is updated as well.
> weird trip-ups
> this editor component also auto-formats which doesn't really work live
> when someone is typing and the value gets replaced simultaneously
So far it all sounds like it's a fundamental problem with that ace-editor
and not with how you use it.
Or maybe it's your (con/write-str format ...)
- if so, then just don't use that function until the editor is out of focus.
I don't think this is an issue with specifically this editor. This is an inherent issue if you replace the content while typing and reset the cursor position
The problem is that changes - even though they are already reflected in the editor - cause the component to re-render.
> Or maybe it's your (con/write-str format ...)
- if so, then just don't use that function until the editor is out of focus.
Also this sounds great but it will still be re-rendered regardless if I deref the data atom anywhere in the renderer + I would need to keep track of the entire editor's state and restore it every time an unnecessary update is triggered. This just seems really bad.
I think my best bet is probably still shouldComponentRender(). So I'll just ask about that instead:
(defn test-comp []
(r/create-class
{:display-name "test-comp"
:reagent-render (fn [] [:strong (pr-str @commands)])
:should-component-update (fn [_ _ _] (println "NO IT SHOULDNT") false)}))
I have this component as a test and I'm wondering what's wrong with it, because the :should-component-update
function never seems to be called and it always re-renders.> it will still be re-rendered What is the exact issue with just re-rendering itself? > I would need to keep track of the entire editor's state and restore it every time an unnecessary update is triggered. This just seems really bad. Not the entire state - just the one you feed it from the outside.
Also I'd have to store other state that I shouldn't have to care about like cursor position
Not sure why :should-component-update
isn't called, but in Reagent source code it's conditioned on reagent.impl.util/*always-update*
.
Hmm. I might make a minimal app that uses that lifecycle method and even open a GitHub issue if it doesn't work at all
The documentation on this needs to be updated anyway, they call this method component-should-update
in the tutorial
what I did is have the editor component be "uncontrolled" - i.e. it doesn't have its :value
controlled by the ratom
here's sort of what it looked like:
(r/with-let [editor-inst (r/atom 0)
editor-value (r/atom "")]
[:div
[editor {:key @editor-inst
:initial-value @editor-value
:on-change #(reset! editor-value %)}]
[:button #{:on-click (fn []
(reset! editor-value "")
(swap! editor-inst inc))}
"Clear"]])
so the editor would ignore any changes to the value of editor-value
, whether it was made by the editor itself or outside.
but if you click the "Clear" button, it would change the editor-value
and force the editor component to reinitialize with the new value by changing the value passed to its :key
prop
:key
is used by React to determine whether it should maintain the same component instance between renders, you can use this strategy with any component or element to force a remount
What still doesn't sit right with me is the additional coupling this introduces. Idk if that's just me
What I mean is, like, these components should be independent. The only thing they should share and care about is the data atom. It's a bit frustrating to me that there seems to be no "pretty" way to achieve this
most elements on the page we can just blow away and redraw, but text inputs & editors have a ton of internal state - cursor position, text selection, etc.
forms are another place where this "let the DOM take the wheel" approach works better IME
I'm seeing similar behavior... it's not calling the fn I pass to :should-component-update
when I change a ratom
here's a working example:
(defn test-comp
[_]
(r/create-class
{:display-name "test-comp"
:reagent-render (fn [commands] [:strong (pr-str commands)])
:should-component-update (fn [_ _ _] (println "NO IT SHOULDNT") false)}))
(defn app
[]
[test-comp @commands])
this actually matches my expectations from pure React. shouldComponentUpdate
is only called when new props are passed in, not when internal state changes