diff --git a/doc/component.md b/doc/component.md index fb9aa880..cbc6ad5f 100644 --- a/doc/component.md +++ b/doc/component.md @@ -24,7 +24,7 @@ export class Counter extends Component { } increment(delta) { - this.setState({ counter: this.state.counter + delta }); + this.updateState({ counter: this.state.counter + delta }); } } ``` diff --git a/examples/demo/app.js b/examples/demo/app.js index ebe8fc36..fbd7f5e9 100644 --- a/examples/demo/app.js +++ b/examples/demo/app.js @@ -36,7 +36,7 @@ export class App extends odoo.core.Component { } setMessageCount(n) { - this.setState({ + this.updateState({ messages: messages.slice(0, n) }); } @@ -45,7 +45,7 @@ export class App extends odoo.core.Component { const index = messages.findIndex(m => m.id === data.id); const n = this.state.messages.length; messages.splice(index, 1); - this.setState({ messages: messages.slice(0, n - 1) }); + this.updateState({ messages: messages.slice(0, n - 1) }); } increment(delta) { diff --git a/examples/demo/counter.js b/examples/demo/counter.js index b89afea4..be951a18 100644 --- a/examples/demo/counter.js +++ b/examples/demo/counter.js @@ -13,6 +13,6 @@ export class Counter extends odoo.core.Component { } increment(delta) { - this.setState({ counter: this.state.counter + delta }); + this.updateState({ counter: this.state.counter + delta }); } } diff --git a/examples/web/static/src/ts/discuss/clock.ts b/examples/web/static/src/ts/discuss/clock.ts index 9007e09a..45fc6a67 100644 --- a/examples/web/static/src/ts/discuss/clock.ts +++ b/examples/web/static/src/ts/discuss/clock.ts @@ -33,7 +33,7 @@ export class Clock extends Widget<{}, State> { } updateTime() { - this.setState({ currentTime: new Date().toLocaleTimeString() }); + this.updateState({ currentTime: new Date().toLocaleTimeString() }); } startClock() { diff --git a/examples/web/static/src/ts/discuss/counter.ts b/examples/web/static/src/ts/discuss/counter.ts index 555c0af1..4c783cae 100644 --- a/examples/web/static/src/ts/discuss/counter.ts +++ b/examples/web/static/src/ts/discuss/counter.ts @@ -34,6 +34,6 @@ export class Counter extends Widget { } increment(delta: number) { - this.setState({ counter: this.state.counter + delta }); + this.updateState({ counter: this.state.counter + delta }); } } diff --git a/examples/web/static/src/ts/discuss/discuss.ts b/examples/web/static/src/ts/discuss/discuss.ts index 641d1f27..f4db91f9 100644 --- a/examples/web/static/src/ts/discuss/discuss.ts +++ b/examples/web/static/src/ts/discuss/discuss.ts @@ -22,25 +22,25 @@ export class Discuss extends Widget<{}, State> { resetCounter(ev: MouseEvent) { if (this.refs.counter instanceof Counter) { - this.refs.counter.setState({ counter: 3 }); + this.refs.counter.updateState({ counter: 3 }); } } resetCounterAsync(ev: MouseEvent) { setTimeout(() => { if (this.refs.counter2 instanceof Counter) { - this.refs.counter2.setState({ counter: 300 }); + this.refs.counter2.updateState({ counter: 300 }); } }, 3000); } toggle() { - this.setState({ validcounter: !this.state.validcounter }); + this.updateState({ validcounter: !this.state.validcounter }); } toggleColor() { const newColor = this.state.color === "red" ? "blue" : "red"; - this.setState({ color: newColor }); + this.updateState({ color: newColor }); } addNotif(sticky: boolean) { diff --git a/examples/web/static/src/ts/ui/root.ts b/examples/web/static/src/ts/ui/root.ts index 35ad9bbd..d3d52d16 100644 --- a/examples/web/static/src/ts/ui/root.ts +++ b/examples/web/static/src/ts/ui/root.ts @@ -24,7 +24,7 @@ export class Root extends Widget { } mounted() { - this.store.on("state_updated", this, this.setState); + this.store.on("state_updated", this, this.updateState); this.store.on("rpc_status", this, this.toggleLoadingIndicator); this.store.on("update_action", this, this.applyController); if (this.store.lastController) { diff --git a/examples/web/static/src/ts/widget.ts b/examples/web/static/src/ts/widget.ts index 9b6b28cf..10f62567 100644 --- a/examples/web/static/src/ts/widget.ts +++ b/examples/web/static/src/ts/widget.ts @@ -28,10 +28,10 @@ export class PureWidget extends Widget { } return false; } - async setState(nextState: Partial) { + async updateState(nextState: Partial) { for (let k in nextState) { if (nextState[k] !== this.state[k]) { - return super.setState(nextState); + return super.updateState(nextState); } } } diff --git a/examples/web/static/src/xml/templates.xml b/examples/web/static/src/xml/templates.xml index 6effdcee..cd4bf7f2 100644 --- a/examples/web/static/src/xml/templates.xml +++ b/examples/web/static/src/xml/templates.xml @@ -114,7 +114,7 @@ - + diff --git a/src/component.ts b/src/component.ts index 32327612..8befe2e6 100644 --- a/src/component.ts +++ b/src/component.ts @@ -108,10 +108,6 @@ export class Component< mounted() {} - shouldUpdate(nextProps: Props): boolean { - return true; - } - willUnmount() {} destroyed() {} @@ -136,6 +132,7 @@ export class Component< target.appendChild(child.el!); child.__mount(); } + async mount(target: HTMLElement): Promise { const vnode = await this._start(); if (this.__widget__.isDestroyed) { @@ -195,23 +192,8 @@ export class Component< } } - /** - * This is the safest update method for widget: its job is to update the state - * and rerender (if widget is mounted). - * - * Notes: - * - it checks if we do not add extra keys to the state. - * - it is ok to call setState before the widget is started. In that - * case, it will simply update the state and will not rerender - */ - async setState(nextState: Partial) { - if (Object.keys(nextState).length === 0) { - return; - } - Object.assign(this.state, nextState); - if (this.__widget__.isStarted) { - return this.render(); - } + shouldUpdate(nextProps: Props): boolean { + return true; } async updateProps(nextProps: Props): Promise { @@ -223,6 +205,25 @@ export class Component< return shouldUpdate ? this._updateProps(nextProps) : Promise.resolve(); } + /** + * This is the safest update method for widget: its job is to update the state + * and rerender (if widget is mounted). + * + * Notes: + * - it checks if we do not add extra keys to the state. + * - it is ok to call updateState before the widget is started. In that + * case, it will simply update the state and will not rerender + */ + async updateState(nextState: Partial) { + if (Object.keys(nextState).length === 0) { + return; + } + Object.assign(this.state, nextState); + if (this.__widget__.isStarted) { + return this.render(); + } + } + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- diff --git a/tests/component.test.ts b/tests/component.test.ts index 19b5201b..056800f5 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -52,7 +52,7 @@ class Counter extends Widget { }; inc() { - this.setState({ counter: this.state.counter + 1 }); + this.updateState({ counter: this.state.counter + 1 }); } } @@ -103,11 +103,11 @@ describe("basic widget properties", () => { ); }); - test("setState before first render does not trigger a render", async () => { + test("updateState before first render does not trigger a render", async () => { let renderCalls = 0; class TestW extends Widget { async willStart() { - this.setState({}); + this.updateState({}); } async _render() { renderCalls++; @@ -119,10 +119,10 @@ describe("basic widget properties", () => { expect(renderCalls).toBe(1); }); - test("setState does not allow adding extra keys", async () => { + test("updateState does not allow adding extra keys", async () => { const widget = new Widget(env); try { - await widget.setState({ extra: 1 }); + await widget.updateState({ extra: 1 }); } catch (e) { expect(e.message).toMatch("Invalid key:"); } @@ -253,7 +253,7 @@ describe("lifecycle hooks", () => { const widget = new ParentWidget(env); await widget.mount(fixture); expect(hookCounter).toBe(0); // sub widget not created yet - await widget.setState({ ok: true }); + await widget.updateState({ ok: true }); expect(hookCounter).toBe(2); }); @@ -313,7 +313,7 @@ describe("lifecycle hooks", () => { const widget = new ParentWidget(env); await widget.mount(fixture); expect(steps).toEqual(["init", "willstart", "mounted"]); - await widget.setState({ ok: false }); + await widget.updateState({ ok: false }); expect(steps).toEqual([ "init", "willstart", @@ -568,9 +568,9 @@ describe("composition", () => { expect(fixture.innerHTML).toBe( "
1
" ); - await widget.setState({ ok: false }); + await widget.updateState({ ok: false }); expect(fixture.innerHTML).toBe("
"); - await widget.setState({ ok: true }); + await widget.updateState({ ok: true }); expect(fixture.innerHTML).toBe( "
0
" ); @@ -592,10 +592,10 @@ describe("composition", () => { ); const counter = children(widget)[0]; expect(counter.__widget__.isMounted).toBe(true); - await widget.setState({ ok: false }); + await widget.updateState({ ok: false }); expect(fixture.innerHTML).toBe("
"); expect(counter.__widget__.isMounted).toBe(false); - await widget.setState({ ok: true }); + await widget.updateState({ ok: true }); expect(counter.__widget__.isMounted).toBe(true); expect(fixture.innerHTML).toBe( "
1
" @@ -615,9 +615,9 @@ describe("composition", () => { await widget.mount(fixture); const input = fixture.getElementsByTagName("input")[0]; input.value = "test"; - await widget.setState({ ok: false }); + await widget.updateState({ ok: false }); expect(fixture.innerHTML).toBe("
"); - await widget.setState({ ok: true }); + await widget.updateState({ ok: true }); expect(fixture.innerHTML).toBe("
"); const input2 = fixture.getElementsByTagName("input")[0]; expect(input).toBe(input2); @@ -682,7 +682,7 @@ describe("composition", () => { } const parent = new Parent(env); await parent.mount(fixture); - await parent.setState({ numbers: [1, 3] }); + await parent.updateState({ numbers: [1, 3] }); expect(normalize(fixture.innerHTML)).toBe( normalize(`
@@ -713,7 +713,7 @@ describe("composition", () => { const parent = new Parent(env); await parent.mount(fixture); const child = children(parent)[0]; - await parent.setState({ flag: true }); + await parent.updateState({ flag: true }); expect(children(parent)[0]).toBe(child); expect(child.__widget__.isDestroyed).toBe(false); expect(normalize(fixture.innerHTML)).toBe( @@ -848,10 +848,10 @@ describe("other directives with t-widget", () => { expect(fixture.innerHTML).toBe("
hey
"); - await widget.setState({ flag: false }); + await widget.updateState({ flag: false }); expect(fixture.innerHTML).toBe("
"); - await widget.setState({ flag: true }); + await widget.updateState({ flag: true }); expect(fixture.innerHTML).toBe("
hey
"); }); @@ -874,7 +874,7 @@ describe("other directives with t-widget", () => { expect(normalize(fixture.innerHTML)).toBe("
somediv
"); - await widget.setState({ flag: false }); + await widget.updateState({ flag: false }); expect(normalize(fixture.innerHTML)).toBe("
hey
"); }); }); @@ -910,7 +910,7 @@ describe("random stuff/miscellaneous", () => { const widget = new Parent(env); await widget.mount(fixture); expect(fixture.innerHTML).toBe("
abc
"); - await widget.setState({ flag: true }); + await widget.updateState({ flag: true }); expect(fixture.innerHTML).toBe("
abcdef
"); }); @@ -979,10 +979,10 @@ describe("async rendering", () => { const w = new W(env); await w.mount(fixture); expect(n).toBe(0); - w.setState({ val: 2 }); + w.updateState({ val: 2 }); expect(n).toBe(1); await nextTick(); - w.setState({ val: 3 }); + w.updateState({ val: 3 }); expect(n).toBe(2); def.resolve(); await nextTick(); @@ -1019,10 +1019,10 @@ describe("async rendering", () => { const parent = new Parent(env); await parent.mount(fixture); expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("
"); - parent.setState({ flagA: true }); + parent.updateState({ flagA: true }); await nextTick(); expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("
"); - parent.setState({ flagB: true }); + parent.updateState({ flagB: true }); await nextTick(); expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe("
"); defB.resolve(); @@ -1065,12 +1065,12 @@ describe("async rendering", () => { expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe( "
a1
" ); - parent.setState({ valA: 2 }); + parent.updateState({ valA: 2 }); await nextTick(); expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe( "
a1
" ); - parent.setState({ flagB: true }); + parent.updateState({ flagB: true }); await nextTick(); expect(fixture.innerHTML.replace(/\r?\n|\r|\s+/g, "")).toBe( "
a1
"