From 31b57cbb40f68f0cb65e766541b230f3e2d3b20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 3 Jun 2022 15:35:04 +0200 Subject: [PATCH] [FIX] component: fix props comparison code Before this commit, Owl had a bad interaction with the static defaultProps code. The props comparison would be done with the new props object (unmodified) and the current props object (with default props applied), so the comparison would always return false, which in turn, causes additional useless renderings. This commit modifies component node to keep a reference to the (unmodified) props object so we can compare it as expected. --- src/runtime/component_node.ts | 20 +++++--- .../__snapshots__/rendering.test.ts.snap | 24 +++++++++ tests/components/rendering.test.ts | 49 +++++++++++++++++++ 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/src/runtime/component_node.ts b/src/runtime/component_node.ts index 911eeccf..d020e475 100644 --- a/src/runtime/component_node.ts +++ b/src/runtime/component_node.ts @@ -30,15 +30,15 @@ export function useComponent(): Component { /** * Apply default props (only top level). - * - * Note that this method does modify in place the props */ -function applyDefaultProps

(props: P, defaultProps: Partial

) { +function applyDefaultProps

(props: P, defaultProps: Partial

): P { + const result = Object.create(props); for (let propName in defaultProps) { - if ((props as any)[propName] === undefined) { - (props as any)[propName] = defaultProps[propName]; + if (props[propName] === undefined) { + result[propName] = defaultProps[propName]; } } + return result; } // ----------------------------------------------------------------------------- // Integration with reactivity system (useState) @@ -106,7 +106,7 @@ export function component

( if (shouldRender) { node.forceNextRender = false; } else { - const currentProps = node.component.props; + const currentProps = node.props; shouldRender = parentFiber.deep || arePropsDifferent(currentProps, props); } if (shouldRender) { @@ -148,6 +148,7 @@ export class ComponentNode

implements VNode implements VNode implements VNode implements VNode { "A:patched", ]).toBeLogged(); }); + +test("children, default props and renderings", async () => { + class Child extends Component { + static template = xml`child`; + static defaultProps = { value: 1 }; + setup() { + useLogLifecycle(); + } + } + + class Parent extends Component { + static template = xml` + + + `; + static components = { Child }; + + state = useState({ value: "A" }); + setup() { + useLogLifecycle(); + } + } + + const parent = await mount(Parent, fixture); + + expect(fixture.innerHTML).toBe("Achild"); + expect([ + "Parent:setup", + "Parent:willStart", + "Parent:willRender", + "Child:setup", + "Child:willStart", + "Parent:rendered", + "Child:willRender", + "Child:rendered", + "Child:mounted", + "Parent:mounted", + ]).toBeLogged(); + + parent.state.value = "B"; + await nextTick(); + expect(fixture.innerHTML).toBe("Bchild"); + expect([ + "Parent:willRender", + "Parent:rendered", + "Parent:willPatch", + "Parent:patched", + ]).toBeLogged(); +});