Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cool-spies-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-simplikit': patch
---

fix(core/hooks): call cleanup when unmount occurs before async effect resolves
19 changes: 19 additions & 0 deletions packages/core/src/hooks/useAsyncEffect/useAsyncEffect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ describe('useAsyncEffect', () => {
expect(cleanup).toHaveBeenCalled();
});

it('should call cleanup even when component unmounts before async effect resolves', async () => {
const cleanup = vi.fn();
const { unmount } = await renderHookSSR(() =>
useAsyncEffect(async () => {
await new Promise(resolve => setTimeout(resolve, 1000));
return cleanup;
}, [])
);

await flushPromises();
unmount();
expect(cleanup).not.toHaveBeenCalled();

vi.advanceTimersByTime(1000);
await flushPromises();

expect(cleanup).toHaveBeenCalledTimes(1);
});

it('should call effect every rerender when deps are undefined', async () => {
const effect = vi.fn().mockResolvedValue(undefined);

Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/hooks/useAsyncEffect/useAsyncEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ import { DependencyList, useEffect } from 'react';
export function useAsyncEffect(effect: () => Promise<void | (() => void)>, deps?: DependencyList) {
useEffect(() => {
Comment on lines 24 to 25
let cleanup: (() => void) | void;
let isCleaned = false;

effect().then(result => {
cleanup = result;
if (isCleaned) {
cleanup?.();
}
});
Comment on lines 29 to 34

return () => {
isCleaned = true;
cleanup?.();
};

Expand Down
Loading