mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] component can be updated while detached from the main DOM
This commit tries to improve the interactions involving unmounted components, or components mounted in an htmelement which is detached from the main DOM, and rendering actions. The main example is mounting a component in detached div, to prepare all children. If we just mount the component, it will work as expected: the full component tree is rendered in memory, and ready to be really mounted at the desired target. However, if before doing that, we update the component and call render on it (for example, with a change in an observed state), then this rendering will be ignored, and therefore, the full subcomponent tree is not uptodate. This commit will also solve another issue in the compatibility layer in odoo: in the form renderer, we mount components with the adapter in a div, which is not yet attached to the DOM. We then manually call the mounted hook when on_attach_callback is called. This means that before this commit, any change to the components between the initial rendering and the call to mounted will be ignored. As a bonus, this commit has the effect of bringing closer the semantics of render and mount operations, which is certainly good. closes #823
This commit is contained in:
@@ -962,6 +962,69 @@ describe("lifecycle hooks", () => {
|
||||
"parent:patched",
|
||||
]);
|
||||
});
|
||||
|
||||
test("willPatch/patched hook is not called if not mounted in DOM", async () => {
|
||||
const steps: string[] = [];
|
||||
|
||||
class ChildWidget extends Component {
|
||||
static template = xml`<div/>`;
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
steps.push("child:constructor");
|
||||
}
|
||||
mounted() {
|
||||
steps.push("child:mounted");
|
||||
}
|
||||
willPatch() {
|
||||
steps.push("child:willPatch");
|
||||
}
|
||||
patched() {
|
||||
steps.push("child:patched");
|
||||
}
|
||||
}
|
||||
class ParentWidget extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-component="child" v="state.n"/>
|
||||
</div>
|
||||
`;
|
||||
static components = { child: ChildWidget };
|
||||
state = useState({ n: 1 });
|
||||
constructor() {
|
||||
super();
|
||||
steps.push("parent:constructor");
|
||||
}
|
||||
mounted() {
|
||||
steps.push("parent:mounted");
|
||||
}
|
||||
willPatch() {
|
||||
steps.push("parent:willPatch");
|
||||
}
|
||||
patched() {
|
||||
steps.push("parent:patched");
|
||||
}
|
||||
}
|
||||
|
||||
const div = document.createElement("div");
|
||||
const widget = new ParentWidget();
|
||||
await widget.mount(div);
|
||||
expect(steps).toEqual(["parent:constructor", "child:constructor"]);
|
||||
|
||||
widget.state.n = 2;
|
||||
await nextTick();
|
||||
|
||||
expect(steps).toEqual(["parent:constructor", "child:constructor"]);
|
||||
|
||||
// then we remount the component in the dom
|
||||
await widget.mount(fixture);
|
||||
|
||||
expect(steps).toEqual([
|
||||
"parent:constructor",
|
||||
"child:constructor",
|
||||
"child:mounted",
|
||||
"parent:mounted",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("destroy method", () => {
|
||||
|
||||
Reference in New Issue
Block a user