Hello, I am working on an editor. It's map based and it uses re-frame db. It updates the DB with the cursor position and the Inner Html of the active component, this causes the component to rerenders fir every key typed. How do I prevent this components from rerendering but still updates the DB? Or if there's any advice on how to best go about if for better performance, cos this slows the performance too.
Check out how some re-com components are implemented. They track the external value in a separate atom and update the inner component only if that value is changed as compared to the value already used by the inner component.
Thanks @p-himik, The components I mean here isn't like buttons, it's like paragraph, image, etc. Plugins (better that way). I am actually developing a clone to https://notion.so, at least you'd get a better picture of what I'm talking about. Here's a link to the project https://www.flatter.to/editor
I understand, re-com also has similar stuff. Not as full-featured, but it wraps inputs and extends their functionality. It's been some time since I looked at their recent code, but you might be particularly interested in their implementation of the typeahead component.
Yes, I've looked at their code recently. The difference between mind and theirs is that, mine uses vector of ids as children, while theirs uses vector of components as children. I cannot implement mine as so cos, mine deals with instantly changing the children position and restacking. My main issue here, is to `update the app-db without a rerendering`
I don't know what you mean and how handling of children is related to state management. If you set your components in such a way so that they re-render on every external data change, then you cannot keep using re-frame as it was intended and not have your components be re-rendered on data change. You can avoid re-rendering when unrelated data changes - that's what fine-tuned subscriptions are for. You can avoid re-rendering when the external data change coincides with the internal data state - that's what that re-com example is for.
Maybe you don't understand me. On typing, the cursor position is written to the DB with the Inner Html. So what I wanted was, it should save anyways (cos I want it undoable and the cursor should be in position), but not rerender the component.
Saving it does not re-render anything by itself. What causes a re-render is a deref of that state with a subsequent usage of that state. An wrapper component with an intermediate extra ratom solve that issue.
A wrapper component? Can you share a simple example? Or explain better pls
Here: https://github.com/day8/re-com/blob/master/src/re_com/typeahead.cljs#L262
The real component is input-text. typeahead is a wrapper with a few fancy bells and whistles, among which is that intermediate state which prevents input-text from being re-rendered unless input-text-model changes.
I so have something like this.
But my component is a componentEditable span that takes the a map of values like id, text, event handlers etc as parameter. So my issue is, how do I save the Inner Html (instantaneously) to the DB without rerendering the span.
Or how do I go about the wrapper you're suggesting?
I t has to go to the DB directly (instead of a state/model) cos of undo for re-frame db
That statement doesn't make sense. If you undo the db, it's exactly the same as resetting the whole db. So it's the same as changing the data directly - there's 0 difference.
It seems you're not understanding me. Each plugins is in a different folder as a component. They share the DB as their state. So if I define a state in the component and save the undos in that state, I can't undo it in another component (so I had to use the app-db undo function instead), also I have to save the keystrokes to the DB to make that possible.. I don't have a problem with the undo as it resets the whole app. My issue is, as I type, the component rerendering, cause some abnormal functionalities in that given plugin. That's why I want to find a way to prevent the rerendering as I type. But it should still save the keystrokes anyways, so as I can undo the app anytime
If you insist that I don't understand you, then I'm afraid I'm out of suggestions except for the already given ones.
OK thanks. I'll still look to it
> My issue is, as I type, the component rerendering Because you ask for it. With React, if a input is in controlled mode (ie. you give it a value), you will receive events but nothing will rerender the component, unless you set a new value. So be sure you don't update any state locally and only update the db. Then if you store the new value + the selectionStart index you can set them while rendering and the undo will work. By the way if you want to prevent the cursor (selectionStart prop) to move without typing anything, you can't afaik. But do you really want to undo this?
Undo works fine (I've coded that even with the cursor position).
I just want to update the DB without rerendering the component.
"only update the DB". Do you mean I should use (update db... Instead of (assoc-in db...to prevent rerendering? Cos currently, only updating the app-db using the later, not a local state.
It doesn't matter if you use update or assoc or any other function or method of creating a DB-like value.
What matters is how the new state compares to the old state.
And what Alexis meant is that there's also local state that you should not be touching (well, according to Alexis - I don't know why local state would be important here by itself).
> I just want to update the DB without rerendering the component. It's not rerendering because you typed.
Maybe I didn't mention I'm dealing with contenteditable and not a controlled input like textarea or input box.
I'm using on-keyup event to update the db
(defn handle-change [event]
(js/console.log event))
(defn simple-component []
(js/console.log "render")
[:input {:value ""
:on-change handle-change}])
render is never called (just the first time)but you have the events
(you can change on-change to on-key-up it does not matter)
Maybe you use is it as uncontrolled so you must make it controlled then
I don't think this works with re-frame, cos I get the previous value from the app-db.
Also, it's html, so I'm using dangerouslySetHtml
My input is a contenteditable span, not a regular input cos it's an Editor
Why do you think it matters?
That's what is baffling me too
Ehm. That's the thing - it doesn't matter. So unless I'm gravely mistaken, you're doing something wrong and not how re-com handles things.
oh ok for contenteditable you can use the onBeforeInput event
I'm not using re-com, I'm using pure hiccup
One should still be able to use onInput, just without changing the contents externally on every edit.
> I'm not using re-com, I'm using pure hiccup
I didn't mean literally, I meant it as an example that I gave earlier in this thread.
Re-com uses a very particular and very common approach of an intermediate ratom. It doesn't sound like you're using that approach.
The only productive thing left to do here, unless you figure it out on your own, is to create an MRE where you did everything you could and share it here so that we can see what's wrong with it and offer a solution.
@schad.alexis Just in case I'm missing something - how would onBeforeInput help here when the current code with onInput doesn't produce the desired result?
since he doesn't want an intermediate state, he can cancel the event so the span is not updated (with preventDefault)
The explanation makes me think that we're seeing the original problem from two completely different perspectives. :) I'll wait for an MRE with a proper README.
I didn't want him to rewrite his state management from start. I do use an intermediate state for my project (and even with a debounce strategy for updating the store)
Having an intermediate state is possible on top of re-frame, without any hassle. I use re-com with re-frame just fine.
I don't think you need an intermediate state if you store every single change in the db (I do not recommend to do that tho)
You don't need it with :input and :textarea because Reagent wraps them automagically.
You do need it with any component that's not based on those that has internal state that will be dropped by an update to its model.
So e.g. you do need it with the typeahead component from re-com since, while it does use an :input, that's just a part of its overall input capabilities.
You also need it with inputs from React libraries since <input>s produced by those can't be touched by Reagent. There's an alternative there sometimes, when such an input provides a way to pass your own <input> implementation. But that's beside the point.
I don't get it, but maybe we don't speak about the same thing. for me, intermediate state is inside the components you describe.
Yes, like here:
(defn my-input [value on-change]
(r/with-let [inner-model (r/atom value)]
(fn [value on-change]
...)))but the fact you said > You do need it for components that have it, I don't understand
Your point of view was inside the component?
Like with a contenteditable span, if you store every single change in the db, I don't see why you would have an intermediate state.
If you override the external model of a component, it will be re-rendered even if its "internal-internal" state perfectly matches that model. So without those hacks for :input and :textarea, doing the most obvious thing with [:input {:on-change #(reset! state %)} @state] would reset the text cursor position to the start on every input. Those Reagent's hacks fix that. An intermediate model in a component wrapper also fixes that by avoiding unnecessary external changes to the "internal-internal" state.
Why would you do that? [:input {:on-change #(reset! state %)} @state]
That's just a contrived example. Re-frame with a plain sub+event is the same thing, conceptually.
Also, you can watch ratoms. Not that I recommend it.
Hmm yes but in the OP case the cursor position is stored (for better undo I guess) so that's not an issue as the cursor is "controlled"
Sorry, I mentioned cursor position only because it's the tangible symptom of the issue. The root issue is the extra re-rendering that the OP wants to get rid of.
I think we won't understand each other (I don't get where you would have an extra render). And we agree to use intermediate states so that's fine.