cjohansen / replicant

A native ClojureScript virtual DOM renderer - render hiccup directly
223 stars 10 forks source link

Lifecycle events, element ids, mount, unmount and attrs-updated #23

Open davesann opened 8 months ago

davesann commented 8 months ago

I am experimenting with replicant and came across the following difficulty.

If I change a "view" from say: [:p {:id "a"}]

to [:p {:id "b"}]

Then the lifecycle events that are generated are :replicant.life-cycle/update and the :replicant/details is [:replicant/updated-attrs]

for most attributes, this makes sense. But for ids, maybe not.

I want to know when a node with a given id is added and when it is removed - so that I can take actions to do other things if needed (specifically I was doing this in the context of a canvas and managing an animation activity).

I was thinking of an id change as a mount/unmount activity - at least conceptually.

The updated-attrs do not convey what changed or what attributes were before. So it's not possible ascertain if something with a particular id was removed with attrs-updated. (for adding, the node id can be accessed in one of the handlers or passed in the handler-data)

I came up with workarounds in this case but am interested to know if this has come up for you and what you would do.

How would you approach initiating processes or recording data when an element that is specifically identifiable - with element.id or something else - is added and removed?

General Ideas:

  1. Treat elements with ids as special attribute cases. 1.1 Maybe trigger new event types - component-added/component-removed ? 1.2 Or use mount/unmount
  2. Include which attributes changed and before and after values when attributes are updated. So the handler can look at them. 2.1 Could create a performance impact for an edge case. 2.2 This may still be still complex to use in practice because all of the lifecycle types that might occur in different scenarios.

I'm just experimenting so low priority - no rush.

cjohansen commented 8 months ago

Hi, thanks for giving replicant a spin!

The short answer is that you're looking for :replicant/key. Replace id with it and your example will behave exactly as expected.

Regarding the details in the life cycle hook I still haven't decided on what level of detail to provide. There has been talk of removing them as it could impose too many restrictions on how replicant can be tuned. If they are to stay I think I agree that having even more details would be useful. You do get both old and new hiccup though, so could possibly use those to check for specifics. My feeling is that when you want to do that, you probably meant to do something else, like use keys 😅

davesann commented 8 months ago

Thank you, that is helpful. I had looked the the key previously but it did not appear to work.

It works for the case above. So I assume ok if same parent structure.

I got the following minimal test case where this does not work for me:

[:div
   [:p {:replicant/key :a
        :replicant/on-render println}
    "A"]]

and

[:p {:replicant/key :b
     :replicant/on-render println}
  "B"]

in this case, switching between these:

is this expected?

cjohansen commented 8 months ago

No, that is a bug. It seems Replican't isn't calling unmount hooks on the children of the node that is removed. That should be fixed 👍