maxence-charriere / go-app

A package to build progressive web apps with Go programming language and WebAssembly.

Home Page:https://go-app.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Event listeners disappear

nathanhack opened this issue · comments

I have a list of app.Compo's that are a generic "Node" and I render a list ([]*Node) of them.
When deleting one of the Nodes (from the middle of the list) like this:

sc.nodes[i] = sc.nodes[len(sc.nodes)-1]
sc.nodes = sc.nodes[:len(sc.nodes)-1]

The event listeners seem to no longer work. Based on the inspect view it looks like there are listeners registered. (Are they from the other Node that was removed?)

Any help would be appreciated.

Currently my work around for this is to have one render() that returns and empty app.Div. Then upon the next render() it renders normally. But this causes flickering and it seems janky, there's gotta be a better way to handle this

Try adding a scope ton the event handler (like its index).

@maxence-charriere Thanks for responding! I find code usually helps in the discussion. When ran there is a graph with nodes (circles and squares). To generate the error click any node above the bottom one, then press del on your key board.

image
After deleting:
image

Then the affected bottom node no longer responds to the OnMouseEnter, etc events.

Try adding a scope ton the event handler (like its index).

I'll look into this.

I was going to ask for some code to help out, thanks for providing. However your svg library looks like it is not public:

go: github.com/nathanhack/go-app-svg@v0.0.0-20230926022915-827a6ad29ea8: ....... exit status 128:
        remote: Repository not found.
        fatal: repository 'https://github.com/nathanhack/go-app-svg/' not found

I was going to ask for some code to help out, thanks for providing. However your svg library looks like it is not public:

go: github.com/nathanhack/go-app-svg@v0.0.0-20230926022915-827a6ad29ea8: ....... exit status 128:
        remote: Repository not found.
        fatal: repository 'https://github.com/nathanhack/go-app-svg/' not found

Sorry about that. I have fixed it. Please try again.

It looks like the mouse over event listeners are still there and fire correctly but are not able to update the go-app element to the correct state to affect the css. Changing Node.Highlighted to Node.highlighted seems to fix your issue.

diff --git a/main.go b/main.go
index c68a1b2..825d8a4 100644
--- a/main.go
+++ b/main.go
@@ -33,7 +33,7 @@ type Node struct {
 	Connections map[int]*Node
 	X, Y        int
 	Selected    bool
-	Highlighted bool
+	highlighted bool
 }

Ideally you would return different app.UI types for svg.Rect and svg.Circle which would force go-app to replace the dom elements ( and their attributes ) with the correct values. See Issue 628 for a deeper dive into what is possibly happening here.

@mlctrez Thank you for your thoughtful response.

It looks like the mouse over event listeners are still there and fire correctly but are not able to update the go-app element to the correct state to affect the css. Changing Node.Highlighted to Node.highlighted seems to fix your issue.

I'm not completely sure why the highlighting stops working if exported. Seems like the OnMouse events should have updated it (exported or not).

Ideally you would return different app.UI types for svg.Rect and svg.Circle which would force go-app to replace the dom elements ( and their attributes ) with the correct values. See Issue 628 for a deeper dive into what is possibly happening here.

This also seems related to my issue #895.

After reading issue 628 it feels like my issue here is the Model and View are the same set of components and that doesn't play well. Meaning I should have some sort of package global (or set of globals) and in the Render use that to generate my components.

Would having empty components prevent updates from ever happening?

I'm not completely sure why the highlighting stops working if exported.

A bit that I left out is that adding some logging a the top of the Render() method:

fmt.Println("Node.Render", n.Index, n.NodeType, n.Highlighted, &n)

results in the following. This was a mouse over mouse out of the 3rd from the end and last ( not working ) VarNode.
image

So when Render() is called, Highlighted has not been set to true on the node, even though the onMouseOver event updated something.

This is why I thought this is probably related to 628, since deleting elements in the middle or start is problematic but not when deleting from the end.

Would having empty components prevent updates from ever happening?

Not sure what you mean here. Empty as in no attributes like Selected, Highlighted?

A bit that I left out is that adding some logging a the top of the Render() method:

fmt.Println("Node.Render", n.Index, n.NodeType, n.Highlighted, &n)

results in the following. This was a mouse over mouse out of the 3rd from the end and last ( not working ) VarNode. image

So when Render() is called, Highlighted has not been set to true on the node, even though the onMouseOver event updated something.

Man, this tree-diff-copy thing is killing me, lol. I think I'm getting the gist.

Would having empty components prevent updates from ever happening?

Not sure what you mean here. Empty as in no attributes like Selected, Highlighted?

I meant something like

type Node struct{app.Compo}

func (*Node) Render() app.UI{
      // stuff updated from package scoped state 
}

But the tree-diff-copy thing won't do an update unless there is something exported that is different. Is there a way to "force" a component to be updated regardless of internal state? I guess I could use a exported int and increment every time I want it updated, but that's just bad.

won't do an update unless there is something exported that is different

There is an update triggered after nodes change here and here.

I think it is less about an update not happening and more about the update not working as desired since exported values are copied from an older element to the new element and they don't line up with the desired state.

The moutpoint from @oderwat is a way to prevent that. Possibly it could be incorporated at some level in the element hierarchy to absolutely force the replacement of dom elements with the correct state.

I hope this make sense and has not added more to the confusion 😕 .

Try adding a scope ton the event handler (like its index).

@maxence-charriere So originally looked at the docs for scope and had no clue how to use it. But after a few searches I found this where it's explained to use a string of the pointer address. It doesn't seem help the issue. But, maybe I'm using it incorrectly?
I've tried both just passing the pointer of the component as the scope and the string rep of the pointer address (fmt.Sprintf("%p", n)).

I think it is less about an update not happening and more about the update not working as desired since exported values are copied from an older element to the new element and they don't line up with the desired state.

I hope this make sense and has not added more to the confusion 😕 .

@mlctrez Thanks for the response. I updated the repo with the prints you suggested from above. And it's clear that the components I create initially are used but then when one of them is deleted, on the next render go-app is still using the one that was deleted but it's moved it to the last(bottom) node. Then on the next mouse event it updates the delete node field but .Update() is called so it then pushes down from the list of nodes which is not the node in go-app's tree so .Highlight gets reset to false. Making it appear as though the mouse event wasn't properly communicated. Thus, unexporting the .highlight field causes the mouse in/out events to have a visual result.

Which is fine, but it forces me to make all fields for event states private and all other fields (needed for the View) must be public and kept in a global variable so that it gets updated from events and pushed down during render. The heartburn here is there are cases where fields needed only for the "view" will need to be held in the "model", but nothing is perfect.

Is that how one should use the go-app's components?

That has me thinking about when I make a modal component that is generic (like one found in an UI library). If the modal has input from the user I would need to keep a global that gets updated because I can't be sure my component fields will contain the input.

The moutpoint from @oderwat is a way to prevent that. Possibly it could be incorporated at some level in the element hierarchy to absolutely force the replacement of dom elements with the correct state.

Thanks for the suggestion! I'll take a look at the moutpoint, and see how it might be used.

I think #767 is also related. I was using that patch to fix some problems I had in test code. We obviously won't use it in production code, but I still have a feeling that there is something that @maxence-charriere could "make better" ?

@oderwat Thanks for the reference to #767. I want to spend a little time looking at it. But given what @maxence-charriere has said on other tickets I believe he cares about performance maybe there's a performance concern, or a worry about other edge cases, I don't know. There's a lot of people potentially using this so changes should be measured for sure. To me, for the issue referenced in #767 and my ticket, it seems like the easiest thing would be: if the pointer is different take the new object. And only look at the children if they're the same pointer. Then the work of performance is on the developer. Moreover there could be a hint (list of updated fields) parameter to Update that if it's called it only updates those fields, and only do the automatic version if not there. But that said, there are a lot of things to consider that I'm completely unaware of. One positive thing i'm taking away from this issue ticket is: there is a go-app community willing to help people. I think for this issue ticket my problem is solved as: user error.

I greatly appreciate the help guys!