UNPKG

use-async-effekt-hooks

Version:

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

106 lines (105 loc) 3.97 kB
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; import { useRef } from "react"; var sameDeps = function (a, b) { return a.length === b.length && a.every(function (v, i) { return Object.is(v, b[i]); }); }; // Global cache for async memoization var asyncMemoCache; function getCache() { if (!asyncMemoCache) asyncMemoCache = new Map(); return asyncMemoCache; } function getCacheKey(factory, deps, scope) { return JSON.stringify([factory.toString(), deps, scope || ""]); } /** * @experimental This hook is experimental and should be used with caution. * * A hook for memoizing async computations that integrates with React Suspense. * * This hook allows you to perform an asynchronous operation and suspend the component * until the operation is complete. It's useful for data fetching or any other async * task that needs to be resolved before rendering. * * In SSR environments (e.g., Next.js), the hook always returns `undefined` on the * server for prerendering. This means the suspense fallback will be displayed on * hydration, and nothing will be displayed on the server-side render. * * This hook requires to be used in a client component. * * @param factory - The async function to execute. * @param deps - The dependency array for the memoization. * @param options - An optional options object. * @param options.scope - An optional scope to isolate the cache. * @returns The memoized value, or it suspends the component. * * @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> * ); * } * ``` */ export function useAsyncMemoSuspense(factory, deps, options) { if (deps === void 0) { deps = []; } // this is just to force the using component to be a client component useRef(undefined); var isClient = typeof window !== "undefined"; if (!isClient) return undefined; var cacheKey = getCacheKey(factory, deps, options === null || options === void 0 ? void 0 : options.scope); var cacheEntry = getCache().get(cacheKey); // Check if dependencies have changed or no cache entry exists if (!cacheEntry || !sameDeps(cacheEntry.deps, deps)) { var promise = Promise.resolve(factory()); var newCacheEntry_1 = { status: "pending", promise: promise, deps: __spreadArray([], deps, true), }; newCacheEntry_1.promise .then(function (result) { return Object.assign(newCacheEntry_1, { status: "success", result: result }); }) .catch(function (error) { return Object.assign(newCacheEntry_1, { status: "error", error: error }); }); cacheEntry = newCacheEntry_1; getCache().set(cacheKey, newCacheEntry_1); } if ((cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.status) === "success") return cacheEntry.result; if ((cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.status) === "error") throw cacheEntry.error; throw cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.promise; }