Fork me on GitHub
#reagent
<
2024-03-02
>
Jérémie Pelletier14:03:39

Is there a way to change the comparison operation of ratoms, without creating a custom implementation from scratch?

Jérémie Pelletier14:03:06

Say it has to store a JS object, which seems to only trigger from identity checks (ie same object but changed props doesn't trigger, but entirely new object does); use-case is storing objects coming from browser APIs directly, without the js->clj transform (because it loses the associated state)

p-himik14:03:16

Not possible. I would definitely reconsider the approach, even if it were possible.

Jérémie Pelletier15:03:16

Yeah it's one of these cases being limited by the existing API; think not having control over the objects, but having it over the function updating them, where it can trigger the watchers

p-himik15:03:48

It's a sensible limitation though: • Changing the comparison algorithm will change how your whole app works, with potential unforeseen consequences • What you have encountered is very, very rare • There are ways around it that are very easy to implement

Jérémie Pelletier15:03:07

ah i don't mean globally change it, but only for a specific ratom

Jérémie Pelletier15:03:37

kinda like react let you set the comparison function per-component, but in this case it happens at reagent's level

Jérémie Pelletier15:03:15

the default is good in the general case, but not in custom domain specific ones

p-himik15:03:53

You can use React's lifecycle functions just fine. And hooks. If feasible, I would still go with some explicit "dummy" ratom that stores all values that, when changed, should lead to a re-render. It would only be deref'ed in the view but not actually used. What's used is the ratom (which can now potentially be a regular atom) that stores the original data.

Jérémie Pelletier15:03:51

ah yeah, this all happens before hitting react at all, doing it in components would duplicate the checks

Jérémie Pelletier15:03:24

i had some success creating a custom ratom type, but it also requires copying private reagent implementation functions into app namespaces

p-himik15:03:18

> ah yeah, this all happens before hitting react at all That puzzles me. You want to trigger a re-render but before React is reached?.. What are you actually trying to do?

Jérémie Pelletier15:03:58

ratoms can be chained, not all resets and swaps get to react

p-himik15:03:41

So it's not about re-rendering at all but about triggering a watcher somewhere? The same mechanism could be used then - wrap the original and the "dummy" ratom in a reaction, that's it.

Jérémie Pelletier15:03:02

ah yeah i could look into that

Ertugrul Cetin17:03:15

I'm working on a reagent project where I need to initialize a component, [canvas-wrapper], only once and then render it within two different parent components, depending on the application state. The goal is for canvas-wrapper to remain mounted at all times, avoiding re-initialization or remounting as the state changes between the two parent components. However, in my current implementation, the component obviously gets recreated each time the state changes. I'm looking for a way to maintain the same instance of canvas-wrapper across these two different components without it being re-initialized upon state changes. Any suggestions? Here's a simplified version of the code illustrating the issue:

(if @show?
  [panel1 [canvas-wrapper]]
  [panel2 [canvas-wrapper]])

p-himik17:03:59

If the only reason is to preserve the state of the <canvas> element, you can do that without preserving the React instance itself.

Ertugrul Cetin17:03:05

> If the only reason is to preserve the state of the <canvas> elem Yes

p-himik17:03:22

An alternative is to use portals, although I've never used them myself: https://react.dev/reference/react-dom/createPortal I would definitely try to go the simpler route of driving everything with data.

Ertugrul Cetin17:03:09

I tried to use it, but could not manage, also on github search there is no simple/clean CLJS usage of portals

p-himik17:03:40

Yeah, then just store the state in :component-will-unmount and restore it in :component-did-mount. :)

p-himik17:03:21

Or maybe update the state on every change and use the state for the render. Then you don't need lifecycle methods at all.

Ertugrul Cetin17:03:07

Thanks for the info, I'll dig in more into portals. If I manage to accomplish, will update the thread

p-himik18:03:22

Why do you want to avoid storing and restoring the data itself?

Ertugrul Cetin19:03:40

It's not easy, canvas is webgl context

👍 1
Jérémie Pelletier23:03:07

Is there a way to render an existing dom node as the main element of a reagent component?

Jérémie Pelletier23:03:56

element can be moved around the dom (ie instancing the component under another parent) and cannot be recreated

Jérémie Pelletier23:03:29

and preferably without a wrapper div, [:<>] yields a nil (dom-node)

p-himik23:03:50

Same way you'd do it in plain React, so I'd look for that. Also not the question above yours, one option there is using portals. Sounds like they can be useful here too.

Jérémie Pelletier23:03:54

isnt portal the opposite of this? ie adding react children to an existing node outside the component?

Jérémie Pelletier23:03:08

only thing I can find with plain react is dangerouslySetInnerHTML, which is also different, hrm

p-himik23:03:23

Portals are not adding children, they are rendering content from one instance inside another instance. Assuming the existing DOM node also comes from React, that's exactly what you need. If the DOM node is outside of React, then you can simply add it as a child to a DOM node managed by React. One huge problem - if the React instance is unmounted, that DOM node will also be lost.

Jérémie Pelletier23:03:20

yeah its outside react

Jérémie Pelletier23:03:08

I think I made another ugly hack that works:

(rc/create-class
   {:component-did-mount
    (fn [this]
      (let [el (rd/dom-node this)]
        (.. el -parentElement (replaceChild test-el el))))
    :component-will-unmount
    (fn [this]
      (.. test-el -parentElement (replaceChild (rd/dom-node this) test-el)))
    :reagent-render
    (fn []
      [:div])})

Jérémie Pelletier23:03:43

it wastes an extra <div> that's unused, but seems to fool react into hosting it without errors on unmount

p-himik23:03:31

That's pretty much the solution, yeah. Although I'd use refs insted of rd/dom-node since it's deprecated.

Jérémie Pelletier23:03:18

ah for this im not worried about updating the react version, dom-node is much simpler than refs