-
-
Notifications
You must be signed in to change notification settings - Fork 144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lifecycle design changes (persistent component instances) #130
Conversation
Prior to this change a component's instance would be outright replaced if the parent rendered a new component pointer; now instead of doing that, we follow the react-like approach of keeping the old component pointer (the one first rendered) as the persistent component instance. The parent properties are copied over using reflection. See #115 (comment) for further details.
Codecov Report
@@ Coverage Diff @@
## master #130 +/- ##
=========================================
- Coverage 64.72% 61.02% -3.7%
=========================================
Files 5 5
Lines 309 331 +22
=========================================
+ Hits 200 202 +2
- Misses 97 116 +19
- Partials 12 13 +1
Continue to review full report at Codecov.
|
Also see this branch which has one additional commit, which I don't intend to merge (for obvious reasons). It's a test / example file I used for developing this: https://github.com/gopherjs/vecty/tree/lifecycle-test |
dom.go
Outdated
comp.Context().prevComponent = comp | ||
comp.Context().prevRenderComponent = doCopy(comp) | ||
// | ||
// TODO: In the event that this really is the previous component instance, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm like 80% certain this is the cause of the issue mentioned in this TODO comment of my test file: 7fc6935#diff-59654adfef9097fef9e9fede9315019aR45
The overarching problem here is that, now that we have persistent component references, it's actually not always obvious where the proper Context()
really is. Someone will need to sit down and rethink how to fix this issue here + hopefully make the Context
storage location a bit easier to think about..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect the behaviour that the comment refers to is a bug I'm already aware of, where a rerender gets queued for a child component that in the mean time gets removed or replaced in the DOM by a parent rerender. If this is indeed the same bug, it will be fixed by lifecycle event changes (mount/unmount) that I already have in the pipeline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I don't think that is it that. The bug here is that:
- Imagine a two-component setup: a parent, and its child.
Rerender(thePrevParent)
is called, wherethePrevParent
is the 'previous' component instance (one the user has a pointer to).renderComponent(thePrevParent, ...)
will be called and so the 'previous' component instance (thePrevParent.Context()
) will be updated on lines 467-469 below.- When its children are walked, it will effectively invoke
renderComponent(theNextChild, thePrevChildContext, thePrevChildRender)
, withtheNextChild
being the newly created component instance -- NOT the persistent / previous component instance. So the 'next' component instance (theNextChild.Context()
) will be updated on lines 467-469.
To be entirely honest, this is hard for me to reason about, and that's part of the problem^. The troublesome bit here is that... it's not clear whether or not the 'next' or 'previous' Component.Context
has the information and which needs to be updated.
I'll play around with this some more to see if I can resolve this issue / remove the ambiguity..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pdf here is what I've managed to get. It's EOD Sunday for me, and I'm not sure when I'll have time again to dive into Vecty code (probably a few weeks). In an effort to not block you from making changes, I suggest:
Just let me know which you prefer ^^ |
Will be next weekend before I can review and make a determination, thanks @slimsag |
…nt component instance) This fixes the one bug mentioned at #130 (comment)
Sorry for the last-minute change BTW, hope that isn't too confusing. ^^ let me know what you think.. I'm all done making changes now.. |
No problem, however I think there are some bad assumptions made, particularly in the latest commit - I don't believe it accounts for the possibility that a child may change types between renders, and there are no checks for component type equality. I've taken your commits that clean up the Let me know what you think, and we can continue either here or there, based on your thoughts. |
Changes imported via: ``` git diff lifecycle..pdf/persistent_component_lifecycle > pdf.patch git checkout lifecycle && git apply pdf.patch git checkout -- doc/ # keep PR reference number correct ``` I then made the following changes: 1. Rename `Core.prevComponent` -> `Core.prevRenderComponent` once again in `dom.go`. 2. `git checkout -- dom_test.go` <- renaming `prevComponent` to `prevRenderComponent` once again. 3. Exclude change in `markup.go` (will send separate PR to discuss this further).
I wasn't quite ready for this to be merged to master, as there are some outstanding things to discuss, mostly around what I'll cover in #132. |
@pdf apologies for the premature merge, I figured that me and you both felt pretty confident about this implementation and, even if not complete due to things like #132, merging would be OK given Vecty hasn't had a 1.0 release yet. I should have waited for your go ahead, though, I'll do that next time! If desireable, we can temporarily revert out of |
No problem, I should have been explicit about my opinion of the code state. My concern is mostly for people trying to take Vecty for a spin and running into the inconsistent and hard to diagnose behaviour that this change may exhibit currently. |
The changelog says:
What are properties and what are non-properties? How do I know if a field should be marked with that struct field tag? |
Fields that are updated from the parent need to be tagged:
|
This is needed to have correct rendering behavior after the breaking API change of hexops/vecty#130. All of these field are properties that are set by external components, they are not internal state of the component itself. See hexops/vecty#149 (comment): > anywhere a Component is instantiated inside Component.Render(), > with fields set from the parent, those fields need to be tagged > with `vecty:"prop"`. And hexops/vecty#130 (comment): > Fields that are updated from the parent need to be tagged: > > type InnerComponent struct { > vecty.Core > ParentSet bool `vecty:"prop"` > localState bool > } > > func (c *InnerComponent) Render() *vecty.HTML { > c.localState = !c.localState > return elem.Div( > vecty.If(c.ParentSet, elem.Span(`true updated from parent`)), > vecty.If(c.localState, elem.Span(`true updated locally`)), > ) > } > > type OuterComponent struct { > vecty.Core > flip bool > } > > func (c *OuterComponent) Render() *vecty.HTML { > c.flip = !c.flip > return elem.Div( > &InnerComponet{ParentSet: c.flip}, > ) > } Helps hexops/vecty#149. Follows e7b1bc5. /cc @slimsag @pdf
Fix hexops#174 This interface has been removed in PR hexops#130, it shouldn't be used anymore.
Fixes #115