As today, the order of those hooks is as following:
constructor() invocation
connectedCallback() host is now in the DOM (no children available yet)
render() method invoked to collect which template to be rehydrated
renderedCallback() notifies that the template returned by render was rehydrated
something changes in the state
render() method invoked to collect which template to be rehydrated
renderedCallback() notifies that the template returned by render was rehydrated
...
...
...
disconnectedCallback() host is not longer in the DOM
It is important to notice that the web components semantics dictates that connectedCallback must be call first on the parent, then on the children, likewise, disconnectedCallback follows the same ordering. In the case of our proprietary hook (renderedCallback), we do it backward, and children are fully rendered before the parent finishes its rendering process.
The Problem
There are actually few problems:
there is a developer expectation that then the component is inserted in the DOM it should be fully functional to avoid the so-called flickering effect.
all frameworks, including those following the WC semantics are doing it that way, in fact webcomponents-everywhere repo is relying on that to test the behavior and compatibility of each framework with real web components since real web components will "mostly" behave like that.
by having the first render() to run before any hook, it makes easier to reasoning about the server side rendering, because that means we can render the entire initial tree without having to invoke any hook at all (very similar to what VUE does).
by having the first render() to run before any hook, it forces developers to do the right thing. Keep in mind that must solutions involving the connectedCallback as the place to set up state before the initial render are just faulty (see example above).
there is an ugly trick used today to do some first time manipulation of the DOM, which requires a flag in the component that will be set only when the first renderedCallback invocation occur, because connectedCallback does not have the children in place just yet.
Example of the wrong assumptions today
export default class Foo extends LightningElement {
@track normalizedItems;
@api items = [];
connectedCallback() {
// oh, I can normalize items here so I don’t need a getter/setter for that because I’m lazy
this.normalizedItems = this.items.map(…);
}
}
then in the parent, I have a button that when pressed, adds a new item to the items list passed down, what are the two problems?
pushing new items into the items in the parent will not rehydrate the parent neither the child, because it is not used during the reactive cycle since connectedCallback is not considered a reactive phase.
changing items will have no effect on the re-normalization of items.
This is a common misconception about connectedCallback.
A Solution
One solution that I was able to test and validate is the inversion of the connectedCallback and the first render() invocation as follow:
constructor() invocation
render() method invoked to collect which template to be rehydrated <---- changed
connectedCallback() host is now in the DOM (all children are now available) <---- changed
renderedCallback() notifies that the template returned by render was rehydrated
something changes in the state
render() method invoked to collect which template to be rehydrated
renderedCallback() notifies that the template returned by render was rehydrated
...
...
...
disconnectedCallback() host is not longer in the DOM
This change is "almost" non-observable for existing code, and I say almost because of the following:
if you have a component that uses the connectedCallback to prepare before the first rendering, it is very likely that your component will render twice (first without the preparation, which is unlikely to throw any error since it is just rendering, and second in the next tick due to mutations observed during the invocation of the connectedCallback). This is very rare, and if it happens, the app will still function.
This should not be a deal breaker though.
Guidelines (that appeared to be missing from the documentation today)
connectedCallback and disconnectedCallback are about the external world, how our component interacts with the UA and the environment that it is dropped into.
renderedCallback is about interaction with the local shadow dom structure.
neither of those hooks should be used for state manipulation.
Current state of the art in LWC
As today, the order of those hooks is as following:
constructor()
invocationconnectedCallback()
host is now in the DOM (no children available yet)render()
method invoked to collect which template to be rehydratedrenderedCallback()
notifies that the template returned by render was rehydratedsomething changes in the state
render()
method invoked to collect which template to be rehydratedrenderedCallback()
notifies that the template returned by render was rehydrated ... ... ...disconnectedCallback()
host is not longer in the DOMIt is important to notice that the web components semantics dictates that connectedCallback must be call first on the parent, then on the children, likewise, disconnectedCallback follows the same ordering. In the case of our proprietary hook (
renderedCallback
), we do it backward, and children are fully rendered before the parent finishes its rendering process.The Problem
There are actually few problems:
Example of the wrong assumptions today
then in the parent, I have a button that when pressed, adds a new item to the items list passed down, what are the two problems?
items
in the parent will not rehydrate the parent neither the child, because it is not used during the reactive cycle since connectedCallback is not considered a reactive phase.This is a common misconception about connectedCallback.
A Solution
One solution that I was able to test and validate is the inversion of the
connectedCallback
and the firstrender()
invocation as follow:constructor()
invocationrender()
method invoked to collect which template to be rehydrated <---- changedconnectedCallback()
host is now in the DOM (all children are now available) <---- changedrenderedCallback()
notifies that the template returned by render was rehydratedsomething changes in the state
render()
method invoked to collect which template to be rehydratedrenderedCallback()
notifies that the template returned by render was rehydrated ... ... ...disconnectedCallback()
host is not longer in the DOMThis change is "almost" non-observable for existing code, and I say almost because of the following:
connectedCallback
to prepare before the first rendering, it is very likely that your component will render twice (first without the preparation, which is unlikely to throw any error since it is just rendering, and second in the next tick due to mutations observed during the invocation of the connectedCallback). This is very rare, and if it happens, the app will still function.This should not be a deal breaker though.
Guidelines (that appeared to be missing from the documentation today)