From 668378b0f52d4756a4a7cad0a2e7c68e71c0f52b Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 22 Sep 2022 12:03:26 +0000 Subject: [PATCH] [FIX] web: useChildRef: handle conditional children MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The useChildRef (used with the useForwardRefToParent hook) allows to retrieve, from a parent component, a t-ref from a child. Consider the following scenario: - have a parent calling useChildRef - have a conditional (with t-if) child calling useForwardRefToParent - in the initial rendering, the child is displayed -> ref.el points to the correct html element - re-render s.t. the child is no longer displayed -> ref.el is null, because the referenced element doesn't exist - re-render s.t. the child is displayed again Before this commit, at this point, ref.el will always be null, even though the corresponding element comes back into the DOM. This commit fixes the issue by correctly updating the ref, each time the useForwardRefToParent hook is called. closes odoo/odoo#100937 X-original-commit: 78d39c79993d1d63c189164f8104e5a3c90f2efd Signed-off-by: Géry Debongnie Signed-off-by: Aaron Bohy (aab) --- addons/web/static/src/core/utils/hooks.js | 4 +- .../static/tests/core/utils/hooks_tests.js | 65 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/core/utils/hooks.js b/addons/web/static/src/core/utils/hooks.js index 2fe6743a5c0..acf1c942a7b 100644 --- a/addons/web/static/src/core/utils/hooks.js +++ b/addons/web/static/src/core/utils/hooks.js @@ -216,7 +216,9 @@ export function useService(serviceName) { */ export function useChildRef() { let defined = false; - return function ref(value) { + let value; + return function ref(v) { + value = v; if (defined) { return; } diff --git a/addons/web/static/tests/core/utils/hooks_tests.js b/addons/web/static/tests/core/utils/hooks_tests.js index 4246df709f4..e0c33f84a30 100644 --- a/addons/web/static/tests/core/utils/hooks_tests.js +++ b/addons/web/static/tests/core/utils/hooks_tests.js @@ -1,7 +1,7 @@ /** @odoo-module **/ import { uiService } from "@web/core/ui/ui_service"; -import { useAutofocus, useBus, useListener, useService } from "@web/core/utils/hooks"; +import { useAutofocus, useBus, useChildRef, useForwardRefToParent, useListener, useService } from "@web/core/utils/hooks"; import { registry } from "@web/core/registry"; import { makeTestEnv } from "@web/../tests/helpers/mock_env"; import { @@ -423,5 +423,68 @@ QUnit.module("utils", () => { assert.rejects(comp.functionService.call("boundThis"), "Component is destroyed"); assert.strictEqual(nbCalls, 8); }); + + QUnit.module("useChildRef / useForwardRefToParent"); + + QUnit.test("simple usecase", async function (assert) { + let childRef; + let parentRef; + class Child extends Component { + setup() { + childRef = useForwardRefToParent("someRef"); + } + } + Child.template = xml`Hello`; + class Parent extends Component { + setup() { + this.someRef = useChildRef(); + parentRef = this.someRef; + } + } + Parent.template = xml`
`; + Parent.components = { Child }; + + const env = await makeTestEnv(); + const target = getFixture(); + + await mount(Parent, target, { env }); + assert.strictEqual(childRef.el, target.querySelector(".my_span")); + assert.strictEqual(parentRef.el, target.querySelector(".my_span")); + }); + + QUnit.test("useForwardRefToParent in a conditional child", async function (assert) { + class Child extends Component { + setup() { + useForwardRefToParent("someRef"); + } + } + Child.template = xml`Hello`; + class Parent extends Component { + setup() { + this.someRef = useChildRef(); + this.state = useState({ hasChild: true }); + } + } + Parent.template = xml`
`; + Parent.components = { Child }; + + const env = await makeTestEnv(); + const target = getFixture(); + + const parent = await mount(Parent, target, { env }); + assert.containsOnce(target, ".my_span"); + assert.strictEqual(parent.someRef.el, target.querySelector(".my_span")); + + parent.state.hasChild = false; + await nextTick(); + + assert.containsNone(target, ".my_span"); + assert.strictEqual(parent.someRef.el, null); + + parent.state.hasChild = true; + await nextTick(); + assert.containsOnce(target, ".my_span"); + assert.strictEqual(parent.someRef.el, target.querySelector(".my_span")); + }); }); });