Skip to content

Commit d1c9956

Browse files
authored
Merge pull request #6 from davemecha/feat/add-use-async-memo-suspense
Feat/add use async memo suspense
2 parents ccbea12 + 1a16898 commit d1c9956

File tree

8 files changed

+885
-6
lines changed

8 files changed

+885
-6
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ jobs:
4141
npm install react@^${{ matrix.react-version }} react-dom@^${{ matrix.react-version }} @types/react@^${{ matrix.react-version }} @types/react-dom@^${{ matrix.react-version }} @testing-library/react@^16.1.0
4242
fi
4343
44-
- name: Run tests
44+
- name: Run tests (excluding Suspense tests for React 16/17)
45+
if: matrix.react-version == '16.8.0' || matrix.react-version == '17.0.0'
46+
run: npm test -- --coverage --watchAll=false --testPathIgnorePatterns=".*useAsyncMemoSuspense.*" --collectCoverageFrom="src/**/*.ts" --collectCoverageFrom="!src/useAsyncMemoSuspense.ts"
47+
48+
- name: Run all tests (React 18+)
49+
if: matrix.react-version == '18.0.0' || matrix.react-version == '19.0.0'
4550
run: npm test -- --coverage --watchAll=false
4651

4752
- name: Upload coverage to Codecov

README.md

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ React hooks for async effects and memoization with proper dependency tracking an
1414
[![GitHub stars](https://img.shields.io/github/stars/davemecha/use-async-effekt?style=social)](https://github.com/davemecha/use-async-effekt/stargazers)
1515
[![issues](https://img.shields.io/github/issues/davemecha/use-async-effekt)](https://github.com/davemecha/use-async-effekt/issues)
1616

17+
Note: Tests are vibe coded. Specific tests are added when bugs are reported.
18+
1719
## Installation
1820

1921
```bash
@@ -310,7 +312,7 @@ module.exports = {
310312
"react-hooks/exhaustive-deps": [
311313
"warn",
312314
{
313-
additionalHooks: "(useAsyncEffekt|useAsyncMemo)",
315+
additionalHooks: "(useAsyncEffekt|useAsyncMemo|useAsyncMemoSuspense)",
314316
},
315317
],
316318
},
@@ -325,7 +327,7 @@ Or if you're using `.eslintrc.json`:
325327
"react-hooks/exhaustive-deps": [
326328
"warn",
327329
{
328-
"additionalHooks": "(useAsyncEffekt|useAsyncMemo)"
330+
"additionalHooks": "(useAsyncEffekt|useAsyncMemo|useAsyncMemoSuspense)"
329331
}
330332
]
331333
}
@@ -360,6 +362,52 @@ This configuration tells ESLint to treat `useAsyncEffekt` and `useAsyncMemo` the
360362

361363
**Returns:** `T | undefined` - The memoized value, or `undefined` while loading
362364

365+
### `useAsyncMemoSuspense(factory, deps?, options?)`
366+
367+
**Parameters:**
368+
369+
- `factory: () => Promise<T> | T` - The async function to execute.
370+
- `deps?: DependencyList` - Optional dependency array (same as `useMemo`).
371+
- `options?: { scope?: string }` - Optional options object.
372+
- `scope?: string` - An optional scope to isolate the cache. This is useful when you have multiple instances of the hook with the same factory and dependencies but you want to keep their caches separate.
373+
374+
**Returns:** `T` - The memoized value. It suspends the component while the async operation is in progress.
375+
376+
**Important Notes:**
377+
378+
- **SSR Environments (e.g., Next.js):** In a server-side rendering environment, this hook will always return `undefined` on the server. The component will suspend on the client during hydration (not on initial render on the server). This means the suspense fallback will be displayed on hydration, and nothing will be displayed on the server-side render.
379+
- **Client Component:** This hook must be used within a "client component" (e.g., in Next.js, the file must have the `"use client";` directive at the top).
380+
- **Experimental:** This hook is experimental and its API might change in future versions.
381+
382+
**Example:**
383+
384+
```tsx
385+
import { Suspense } from "react";
386+
import { useAsyncMemoSuspense } from "use-async-effekt-hooks";
387+
388+
function UserProfile({ userId }) {
389+
const user = useAsyncMemoSuspense(async () => {
390+
const response = await fetch(`https://api.example.com/users/${userId}`);
391+
return response.json();
392+
}, [userId]);
393+
394+
return (
395+
<div>
396+
<h1>{user.name}</h1>
397+
<p>{user.email}</p>
398+
</div>
399+
);
400+
}
401+
402+
function App() {
403+
return (
404+
<Suspense fallback={<div>Loading...</div>}>
405+
<UserProfile userId="1" />
406+
</Suspense>
407+
);
408+
}
409+
```
410+
363411
## Features
364412

365413
- ✅ Full TypeScript support

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
"react": "^18.0.0",
6161
"react-dom": "^18.0.0",
6262
"semver": "^7.7.2",
63-
"ts-jest": "^29.1.0",
63+
"ts-jest": "^29.4.0",
6464
"typescript": "^5.0.0"
6565
},
6666
"jest": {
@@ -77,7 +77,8 @@
7777
"src/**/*.{ts,tsx}",
7878
"!src/__tests__/**/*.{ts,tsx}",
7979
"!src/**/*.d.ts",
80-
"!src/setupTests.ts"
80+
"!src/setupTests.ts",
81+
"!src/**/test-utils.ts"
8182
],
8283
"coverageReporters": [
8384
"text",

src/__tests__/useAsyncMemo.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useAsyncMemo } from "../useAsyncMemo";
44

55
describe("useAsyncMemo", () => {
66
beforeEach(() => {
7+
jest.clearAllMocks();
78
jest.clearAllTimers();
89
jest.useRealTimers();
910
});

0 commit comments

Comments
 (0)