Skip to content
Merged
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
7 changes: 6 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ jobs:
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
fi

- name: Run tests
- name: Run tests (excluding Suspense tests for React 16/17)
if: matrix.react-version == '16.8.0' || matrix.react-version == '17.0.0'
run: npm test -- --coverage --watchAll=false --testPathIgnorePatterns=".*useAsyncMemoSuspense.*" --collectCoverageFrom="src/**/*.ts" --collectCoverageFrom="!src/useAsyncMemoSuspense.ts"

- name: Run all tests (React 18+)
if: matrix.react-version == '18.0.0' || matrix.react-version == '19.0.0'
run: npm test -- --coverage --watchAll=false

- name: Upload coverage to Codecov
Expand Down
52 changes: 50 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ React hooks for async effects and memoization with proper dependency tracking an
[![GitHub stars](https://img.shields.io/github/stars/davemecha/use-async-effekt?style=social)](https://github.com/davemecha/use-async-effekt/stargazers)
[![issues](https://img.shields.io/github/issues/davemecha/use-async-effekt)](https://github.com/davemecha/use-async-effekt/issues)

Note: Tests are vibe coded. Specific tests are added when bugs are reported.

## Installation

```bash
Expand Down Expand Up @@ -310,7 +312,7 @@ module.exports = {
"react-hooks/exhaustive-deps": [
"warn",
{
additionalHooks: "(useAsyncEffekt|useAsyncMemo)",
additionalHooks: "(useAsyncEffekt|useAsyncMemo|useAsyncMemoSuspense)",
},
],
},
Expand All @@ -325,7 +327,7 @@ Or if you're using `.eslintrc.json`:
"react-hooks/exhaustive-deps": [
"warn",
{
"additionalHooks": "(useAsyncEffekt|useAsyncMemo)"
"additionalHooks": "(useAsyncEffekt|useAsyncMemo|useAsyncMemoSuspense)"
}
]
}
Expand Down Expand Up @@ -360,6 +362,52 @@ This configuration tells ESLint to treat `useAsyncEffekt` and `useAsyncMemo` the

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

### `useAsyncMemoSuspense(factory, deps?, options?)`

**Parameters:**

- `factory: () => Promise<T> | T` - The async function to execute.
- `deps?: DependencyList` - Optional dependency array (same as `useMemo`).
- `options?: { scope?: string }` - Optional options object.
- `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.

**Returns:** `T` - The memoized value. It suspends the component while the async operation is in progress.

**Important Notes:**

- **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.
- **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).
- **Experimental:** This hook is experimental and its API might change in future versions.

**Example:**

```tsx
import { Suspense } from "react";
import { useAsyncMemoSuspense } from "use-async-effekt-hooks";

function UserProfile({ userId }) {
const user = useAsyncMemoSuspense(async () => {
const response = await fetch(`https://api.example.com/users/${userId}`);
return response.json();
}, [userId]);

return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}

function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userId="1" />
</Suspense>
);
}
```

## Features

- ✅ Full TypeScript support
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"react": "^18.0.0",
"react-dom": "^18.0.0",
"semver": "^7.7.2",
"ts-jest": "^29.1.0",
"ts-jest": "^29.4.0",
"typescript": "^5.0.0"
},
"jest": {
Expand All @@ -77,7 +77,8 @@
"src/**/*.{ts,tsx}",
"!src/__tests__/**/*.{ts,tsx}",
"!src/**/*.d.ts",
"!src/setupTests.ts"
"!src/setupTests.ts",
"!src/**/test-utils.ts"
],
"coverageReporters": [
"text",
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/useAsyncMemo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useAsyncMemo } from "../useAsyncMemo";

describe("useAsyncMemo", () => {
beforeEach(() => {
jest.clearAllMocks();
jest.clearAllTimers();
jest.useRealTimers();
});
Expand Down
Loading