Fork me on GitHub
#re-frame
<
2023-05-07
>
Nasiru Ibrahim15:05:17

Is there any possible way to update app-db without re-rendering? Atleast I can save every little step and be able to undo and redo. It’s a page builder like notion.so

p-himik15:05:48

You can use level-3 subscriptions without using level-2 subscriptions in views. This way, no re-rendering will be done unless the data used by views is changed.

Nasiru Ibrahim15:05:45

Thank you @U2FRKM4TW you’re really helping everywhere. I appreciate that. But how do I go about the level-based sub?

Nasiru Ibrahim15:05:03

Like, the views get rerendered if I dispatch an event, and this I don’t want it all the time. I want to be able to save the content of my contenteditable div without rerendering the whole view so I don’t miss the cursor position

p-himik16:05:00

> Like, the views get rerendered if I dispatch an event That's not what's going on. At least, not in general. It might be going on in your specific case but only if that view depends on the data that that event changes. In that case, you might want to use an uncontrolled component. Only its initial state will depend on the value you get from a subscription, but after that it will deal with its state on its own and let the rest of the app know about it only via the :on-change handler (or whatever it is for content editable components). (Not that important, but it doesn't happen with inputs and text areas because Reagent explicitly handles them: https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/input.cljs#L137-L140) To do that, you need to deref the value sub outside of the main view function. It can be done with a form-2 or a form-3 component (not tested):

(defn my-content-editable []
  (let [initial-value @(rf/subscribe [...])
        on-input (fn [e] (rf/dispatch [...]))]
    (fn []
      [:div {:content-editable true
             :on-input         on-input}
       initial-value])))
If you would like to have something like this in Reagent itself, it makes sense to comment on this issue: https://github.com/reagent-project/reagent/issues/220

Nasiru Ibrahim16:05:08

In my application, every component is rendered with a recursive function because it’s just a map that get rendered into components based on the key :type in the component map. So if there’s exist any change in the map, the whole components got rerendered so as to initiate the change across. Currently it’s working fine cos I’m using the :on-mouse-leave and :on-mouse-out event to dispatch the latest changes to the specific component’s map in the db which gets rerendered back into the view. But what I really want is a way to dispatch these changes and the component don’t get rerendered yet until I want it to. Why i want to use this approach is because, the user might be typing and wants some steps of undo, something may happen that the client isn’t available to make the mouse events to save the last procedure (just assuming). So I want it to be saving at same time when the user is making changes.

Nasiru Ibrahim16:05:31

Also because of this effect, when I move my mouse out to click maybe a menu so as to make an operation on the current focus index, I have to use a state to restrict the editor from these mouse events before I can do that. It’s a bit frustrating. Or maybe I want to make a change to the state from a different component wher I need to send to the app db , it gets rerendered

p-himik16:05:53

Yeah, passing around the "world map" doesn't work that well with Reagent. The less re-rendering you want to have, the less data you need to pass around. Ideally, every component should receive only the data that it needs, be it via explicit arguments or via subscriptions. (It's alright to pass around data that doesn't change though.) Regarding undo - it's much, much easier to simply let your browser handle that for you. Which should work just fine if all you need is to be able to undo within a single field. Undoing re-frame events is a nightmare in all but small applications.

Nasiru Ibrahim16:05:49

I’ve actually read about the undo dependency in reframe, but it can’t undo what wasn’t updated in the db. And also I can’t be saving to the db, rerendering at same time and expect the cursor at its position. That’s where my problem lies. I want to be able to save as I type, if there’s no rerendering, then the cursor will be preserved. By this, I can undo even if I refresh the page. But in the case of browser handling the undo, once refreshed or blurred, you won’t be able to perform the undo and redo operations on that contenteditable component again

p-himik16:05:50

My point is, you shouldn't be using the re-frame's undo.

p-himik16:05:12

> By this, I can undo even if I refresh the page. Do you really need this functionality? I'm pretty sure even things like Google Docs don't have it.

p-himik16:05:15

> once refreshed or blurred, you won’t be able to perform the undo The "refreshed" part is true. The "blurred" part is not. You can still undo changes if you change the focus and then return it back to the component.

zalky16:05:01

@U046LLBLNJZ, hopefully I haven't misinterpreted your problem and intention, but if you'd just like one component to not re-render even if its parent does, I think you can probably accomplish this using https://react.dev/reference/react/useMemo#skipping-re-rendering-of-components. The basic idea is explained in the link I sent.

Nasiru Ibrahim16:05:02

@U2FRKM4TW It is in notion.so where you can undo even backtrack to a week

zalky16:05:06

Sorry, edit with link.

Nasiru Ibrahim16:05:39

@U0HJK8682 it’s not just about React. It’s about reframe db.

p-himik16:05:20

Just tested notion.so - undo doesn't work across page refreshes. Maybe there's some place where it does work, but it's not their default document. Implementing a comprehensive undo mechanism is hard. Re-frame doesn't make it easier. You'll have to deal with: • Async events that have nothing to do with user input (events that can change unrelated things or change the very input a user is trying to edit right now) • Input across multiple components (and then describing to your users why hitting "Ctrl+Z" while having a text input X in focus undoes changes in a text input Y) • Batching input so that undo isn't done character-by-character in case of keyboard input or pixel-by-pixel in case of mouse input Those are just the things that I have personally had to deal with and that I managed to recall on the spot. And I still don't have that undo mechanism working properly, even though I now have basically a re-frame wrapper that makes such things much easier (all user edits are isolated from other changes, all edits are done in independent "transactions").

Nasiru Ibrahim16:05:54

@U2FRKM4TW notion does. It can make you go back to a history. Which is also possible with reframe, based on my settings. It’s Just to save the map of a particular date and render it when needed. But my issue isn’t that (for a week or days). My issue is the immediate undo of some minutes or seconds ago after it has been rerendered. Just like notion works. If I added a photo just now, I should be able to undo till the photo goes off as it was before I added it. It can be done map-wise but not wise for immediate operations

zalky16:05:18

👆 p-himik makes some really good points about the undo functionality in general. My only contribution is if we're focusing just on your specific problem of dispatching an event that mutates your global state, which in turn is passed as a global map to the root of your component hierarchy, causing it to re-render everything, then to me at least, it seems there's two options: 1. You can redesign your component data dependencies to be more fine-grained, and not a global map that is passed to the root component. This might be ideal, but an unfeasible amount of work. 2. You can try using the React memo feature to wrap your child component and prevent it from re-rendering.

steveb8n07:05:10

I did this with a global interceptor that captures the 10 most recent db values in an atom (not app-db). No re-rendering because no change to app-db and the values can be used to restore app-db later at any time

p-himik08:05:35

IIRC that's exactly how re-frame-undo works.

Nasiru Ibrahim08:05:07

@U0510KXTU so how about after a rerender occurs? Cos in my case, I have multiple components and the only way to keep their values is in a central db (app-db) which will definitely rerender the components. I understand the concept of the atom you mean, but my components are all in different namespaces, some are copies of others, so if I define an atom in any component, it’s copy will have the state too.

Nasiru Ibrahim08:05:32

I’ll reason it deeper for now and see anyway possible

steveb8n08:05:34

The atom is global, only swapped by the interceptor IE decoupled from all components, subs, events and effects. I'd bet this was the motivation for global interceptors

p-himik08:05:08

It wasn't. :)

p-himik08:05:46

But they are much more useful than the initial motivation suggested.

steveb8n08:05:19

I'd be a bad gambler :rolling_on_the_floor_laughing: