diff --git a/.changeset/witty-zoos-peel.md b/.changeset/witty-zoos-peel.md new file mode 100644 index 0000000..341ff5c --- /dev/null +++ b/.changeset/witty-zoos-peel.md @@ -0,0 +1,5 @@ +--- +"haunted": patch +--- + +Prevent infinite loops if effect schedules an update and then throws. diff --git a/src/create-effect.ts b/src/create-effect.ts index 20d99d9..b6a66f5 100644 --- a/src/create-effect.ts +++ b/src/create-effect.ts @@ -21,10 +21,12 @@ function createEffect(setEffects: (state: State, cb: Callable) => void) { } call(): void { - if(!this.values || this.hasChanged()) { + const hasChanged = !this.values || this.hasChanged(); + this.lastValues = this.values; + + if(hasChanged) { this.run(); } - this.lastValues = this.values; } run(): void { diff --git a/test/use-effects.test.ts b/test/use-effects.test.ts index 8b93351..87195de 100644 --- a/test/use-effects.test.ts +++ b/test/use-effects.test.ts @@ -176,4 +176,25 @@ describe('useEffect', () => { expect(parentEffects).to.equal(2); expect(childEffects).to.equal(2); }); + + it("Avoids causing infinite loops when the callback schedules an update, but then throws an exception", async () => { + function App() { + const [state, setState] = useState(0); + + useEffect(() => { + // an update is scheduled + setState((state) => state! + 1); + // an error is thrown + throw new Error("Unexpected error"); + }, []); + + return state; + } + + customElements.define("infinite-loop-test", component(App)); + + const el = await fixture(html``); + expect(el).to.be.ok; + expect(el.shadowRoot!.textContent).to.equal("1"); + }); });