Skip to content

Commit c838e76

Browse files
Release v1.1.0: Add useAsyncMemoSuspense hook with implementation, tests, and documentation (#7)
* feat: add test suite with GitHub Actions workflow and React version compatibility tests * feat: add cross-version React testing compatibility layer and fix type issues * chore: add CodeRabbit config for main and develop branch reviews * feat: add matrix testing across React 16-19 and Node 16-20 versions * fix: add conditional testing library versions based on React version in CI workflow * chore: add @testing-library/react dependency for React version compatibility * test: add comprehensive test coverage for edge cases and error handling in useAsyncMemo and useAsyncEffekt * chore: update Codecov action from v3 to v4 * add comment for any type usage * chore: add strict error handling to matrix test script with set -euo pipefail * fix: add error handling and exit codes to matrix test script * style: improve readability of conditional npm install in Dockerfile.test * chore: remove --force flag from npm install commands in test setup * reset version to pre-release version * fix: downgrade testing-library/react to v16.1.0 for React 19 test script * fix: update test dependencies installation for different React versions in CI/Docker * docs: add clickable links to README badges * chore: add sideEffects:false to package.json for tree-shaking optimization * feat: implement useAsyncMemoSuspense hook with global caching and dependency tracking * feat(hooks): add docs and tests for useAsyncMemoSuspense Adds JSDocs, README documentation, and tests for the new `useAsyncMemoSuspense` hook. The documentation includes examples and important notes about SSR and client component usage. The tests cover suspense, error handling, dependency changes, and scope usage. * refactor: improve test coverage with TypeScript and realistic test scenarios * test: improve timer handling and mock implementations in async memo tests * ci: skip Suspense tests for React versions 16 and 17 in test workflow * chore: update ErrorBoundary props type to use simplified React.PropsWithChildren syntax * ci: exclude suspense module from test coverage collection for React 16/17 * chore: exclude test-utils.ts from code coverage metrics * test: add comprehensive cache behavior and performance tests for useAsyncMemoSuspense * docs: add vibe coded testing note in README * refactor: removing unnecessary comments --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent e5147d7 commit c838e76

File tree

8 files changed

+895
-18
lines changed

8 files changed

+895
-18
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: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22

33
React hooks for async effects and memoization with proper dependency tracking and linting support.
44

5-
![CI](https://github.com/davemecha/use-async-effekt/actions/workflows/test.yml/badge.svg)
6-
![npm](https://img.shields.io/npm/v/use-async-effekt-hooks)
7-
![codecov](https://codecov.io/gh/davemecha/use-async-effekt/branch/main/graph/badge.svg)
8-
![npm downloads](https://img.shields.io/npm/dw/use-async-effekt-hooks)
9-
![MIT](https://img.shields.io/npm/l/use-async-effekt-hooks)
10-
![bundle size](https://img.shields.io/bundlephobia/minzip/use-async-effekt-hooks)
11-
12-
![bundle size](https://img.shields.io/bundlephobia/minzip/use-async-effekt-hooks)
13-
![Types](https://img.shields.io/npm/types/use-async-effekt-hooks)
14-
![react 16-19](https://img.shields.io/badge/react-16‒19-brightgreen?logo=react)
15-
![GitHub stars](https://img.shields.io/github/stars/davemecha/use-async-effekt?style=social)
16-
![issues](https://img.shields.io/github/issues/davemecha/use-async-effekt)
5+
[![CI](https://github.com/davemecha/use-async-effekt/actions/workflows/test.yml/badge.svg)](https://github.com/davemecha/use-async-effekt/actions/workflows/test.yml)
6+
[![npm](https://img.shields.io/npm/v/use-async-effekt-hooks)](https://www.npmjs.com/package/use-async-effekt-hooks)
7+
[![codecov](https://codecov.io/gh/davemecha/use-async-effekt/branch/main/graph/badge.svg)](https://codecov.io/gh/davemecha/use-async-effekt)
8+
[![npm downloads](https://img.shields.io/npm/dw/use-async-effekt-hooks)](https://www.npmjs.com/package/use-async-effekt-hooks)
9+
[![MIT](https://img.shields.io/npm/l/use-async-effekt-hooks)](LICENSE)
10+
11+
[![bundle size](https://img.shields.io/bundlephobia/minzip/use-async-effekt-hooks)](https://bundlephobia.com/package/use-async-effekt-hooks)
12+
[![Types](https://img.shields.io/npm/types/use-async-effekt-hooks)](https://www.npmjs.com/package/use-async-effekt-hooks)
13+
[![react 16-19](https://img.shields.io/badge/react-16‒19-brightgreen?logo=react)](https://react.dev/)
14+
[![GitHub stars](https://img.shields.io/github/stars/davemecha/use-async-effekt?style=social)](https://github.com/davemecha/use-async-effekt/stargazers)
15+
[![issues](https://img.shields.io/github/issues/davemecha/use-async-effekt)](https://github.com/davemecha/use-async-effekt/issues)
16+
17+
Note: Tests are vibe coded. Specific tests are added when bugs are reported.
1718

1819
## Installation
1920

@@ -311,7 +312,7 @@ module.exports = {
311312
"react-hooks/exhaustive-deps": [
312313
"warn",
313314
{
314-
additionalHooks: "(useAsyncEffekt|useAsyncMemo)",
315+
additionalHooks: "(useAsyncEffekt|useAsyncMemo|useAsyncMemoSuspense)",
315316
},
316317
],
317318
},
@@ -326,7 +327,7 @@ Or if you're using `.eslintrc.json`:
326327
"react-hooks/exhaustive-deps": [
327328
"warn",
328329
{
329-
"additionalHooks": "(useAsyncEffekt|useAsyncMemo)"
330+
"additionalHooks": "(useAsyncEffekt|useAsyncMemo|useAsyncMemoSuspense)"
330331
}
331332
]
332333
}
@@ -361,6 +362,52 @@ This configuration tells ESLint to treat `useAsyncEffekt` and `useAsyncMemo` the
361362

362363
**Returns:** `T | undefined` - The memoized value, or `undefined` while loading
363364

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+
364411
## Features
365412

366413
- ✅ 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: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "use-async-effekt-hooks",
33
"version": "1.0.0",
44
"description": "React hooks for async effects and memoization with proper dependency tracking and linting support",
5+
"sideEffects": false,
56
"main": "dist/index.js",
67
"types": "dist/index.d.ts",
78
"files": [
@@ -59,7 +60,7 @@
5960
"react": "^18.0.0",
6061
"react-dom": "^18.0.0",
6162
"semver": "^7.7.2",
62-
"ts-jest": "^29.1.0",
63+
"ts-jest": "^29.4.0",
6364
"typescript": "^5.0.0"
6465
},
6566
"jest": {
@@ -76,7 +77,8 @@
7677
"src/**/*.{ts,tsx}",
7778
"!src/__tests__/**/*.{ts,tsx}",
7879
"!src/**/*.d.ts",
79-
"!src/setupTests.ts"
80+
"!src/setupTests.ts",
81+
"!src/**/test-utils.ts"
8082
],
8183
"coverageReporters": [
8284
"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)