reagent

2023-09-17T16:21:35.098069Z

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.

p-himik 2023-09-17T17:40:58.614739Z

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.

2023-09-17T17:45:48.763789Z

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

p-himik 2023-09-17T17:48:37.190419Z

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.

2023-09-17T17:54:43.210299Z

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`

p-himik 2023-09-17T17:59:31.813929Z

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.

2023-09-17T18:03:58.271909Z

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.

p-himik 2023-09-17T18:08:21.519549Z

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.

2023-09-17T18:17:20.065229Z

A wrapper component? Can you share a simple example? Or explain better pls

p-himik 2023-09-17T18:19:51.494429Z

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.

2023-09-17T19:06:38.475179Z

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?

2023-09-17T19:09:36.235039Z

I t has to go to the DB directly (instead of a state/model) cos of undo for re-frame db

p-himik 2023-09-17T19:14:01.853299Z

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.

2023-09-17T19:20:50.559659Z

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

p-himik 2023-09-17T19:54:00.280279Z

If you insist that I don't understand you, then I'm afraid I'm out of suggestions except for the already given ones.

2023-09-17T20:03:34.933759Z

OK thanks. I'll still look to it

schadocalex 2023-09-18T09:41:29.872459Z

> 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?

2023-09-18T10:02:26.595649Z

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.

p-himik 2023-09-18T10:05:12.812989Z

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).

schadocalex 2023-09-18T10:07:17.421159Z

> I just want to update the DB without rerendering the component. It's not rerendering because you typed.

2023-09-18T10:08:14.100629Z

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

schadocalex 2023-09-18T10:09:41.139979Z

(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)

schadocalex 2023-09-18T10:09:51.267479Z

but you have the events

schadocalex 2023-09-18T10:11:18.286529Z

(you can change on-change to on-key-up it does not matter)

schadocalex 2023-09-18T10:13:00.835229Z

Maybe you use is it as uncontrolled so you must make it controlled then

2023-09-18T10:13:59.748149Z

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

2023-09-18T10:14:44.420269Z

My input is a contenteditable span, not a regular input cos it's an Editor

p-himik 2023-09-18T10:15:22.817249Z

Why do you think it matters?

2023-09-18T10:15:58.741879Z

That's what is baffling me too

p-himik 2023-09-18T10:16:35.647669Z

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.

schadocalex 2023-09-18T10:16:57.715959Z

oh ok for contenteditable you can use the onBeforeInput event

2023-09-18T10:17:22.171969Z

I'm not using re-com, I'm using pure hiccup

p-himik 2023-09-18T10:17:55.591789Z

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.

p-himik 2023-09-18T10:18:16.546779Z

Re-com uses a very particular and very common approach of an intermediate ratom. It doesn't sound like you're using that approach.

p-himik 2023-09-18T10:20:23.224219Z

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.

p-himik 2023-09-18T10:22:30.459769Z

@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?

schadocalex 2023-09-18T10:26:03.646309Z

since he doesn't want an intermediate state, he can cancel the event so the span is not updated (with preventDefault)

p-himik 2023-09-18T10:30:21.320589Z

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.

schadocalex 2023-09-18T10:37:25.999099Z

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)

p-himik 2023-09-18T10:38:48.414429Z

Having an intermediate state is possible on top of re-frame, without any hassle. I use re-com with re-frame just fine.

schadocalex 2023-09-18T11:01:02.229729Z

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)

p-himik 2023-09-18T11:06:27.646769Z

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.

p-himik 2023-09-18T11:08:33.465639Z

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.

schadocalex 2023-09-18T11:10:41.764789Z

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.

p-himik 2023-09-18T11:11:13.195279Z

Yes, like here:

(defn my-input [value on-change]
  (r/with-let [inner-model (r/atom value)]
    (fn [value on-change]
      ...)))

schadocalex 2023-09-18T11:13:55.350639Z

but the fact you said > You do need it for components that have it, I don't understand

schadocalex 2023-09-18T11:14:45.494549Z

Your point of view was inside the component?

schadocalex 2023-09-18T11:17:38.989289Z

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.

p-himik 2023-09-18T11:38:05.681849Z

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.

schadocalex 2023-09-18T12:28:42.720929Z

Why would you do that? [:input {:on-change #(reset! state %)} @state]

p-himik 2023-09-18T12:30:57.846379Z

That's just a contrived example. Re-frame with a plain sub+event is the same thing, conceptually.

p-himik 2023-09-18T12:31:08.365609Z

Also, you can watch ratoms. Not that I recommend it.

schadocalex 2023-09-18T12:44:25.217169Z

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"

p-himik 2023-09-18T12:46:18.789879Z

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.

schadocalex 2023-09-18T13:27:57.492429Z

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.