{"id":15800,"library":"redux-bundler-async-resources","title":"Redux-Bundler Async Resources","description":"This package provides bundle factories for `redux-bundler`, specializing in the management of asynchronous data resources. It offers `createAsyncResourceBundle` for handling single remote resources and `createAsyncResourcesBundle` for managing collections of async resources, each with its own lifecycle, including loading, staleness, and expiration. Key features include configurable `staleAfter` and `expireAfter` durations to manage data freshness and automatic removal, a `dependencyKey` mechanism for conditional fetching and automatic cache invalidation based on upstream selector changes, and support for `doAdjust` actions to optimistically update resource state after mutations. The current stable version is 2.0.1. Releases appear to be ad-hoc, driven by new features or bug fixes, rather than a strict time-based cadence. It differentiates itself by providing a more robust and opinionated approach to async resource management within the `redux-bundler` ecosystem, extending beyond `redux-bundler`'s native capabilities.","status":"active","version":"2.0.1","language":"javascript","source_language":"en","source_url":"https://github.com/abuinitski/redux-bundler-async-resources","tags":["javascript","PWAs","bundler","redux"],"install":[{"cmd":"npm install redux-bundler-async-resources","lang":"bash","label":"npm"},{"cmd":"yarn add redux-bundler-async-resources","lang":"bash","label":"yarn"},{"cmd":"pnpm add redux-bundler-async-resources","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Core framework for creating bundles and managing state.","package":"redux-bundler","optional":false}],"imports":[{"note":"Primary factory for managing single async resources. Use named import.","wrong":"const createAsyncResourceBundle = require('redux-bundler-async-resources').createAsyncResourceBundle","symbol":"createAsyncResourceBundle","correct":"import { createAsyncResourceBundle } from 'redux-bundler-async-resources'"},{"note":"Factory for managing collections of async resources. Introduced in v1.1.0. Note the plural 'Resources'.","wrong":"import createAsyncResourcesBundle from 'redux-bundler-async-resources'","symbol":"createAsyncResourcesBundle","correct":"import { createAsyncResourcesBundle } from 'redux-bundler-async-resources'"},{"note":"Helper for automated processing of single async resource bundle keys. Use named import.","wrong":"import * as bundleKeys from 'redux-bundler-async-resources'","symbol":"makeAsyncResourceBundleKeys","correct":"import { makeAsyncResourceBundleKeys } from 'redux-bundler-async-resources'"},{"note":"Helper for automated processing of plural async resources bundle keys. Introduced in v1.1.1.","symbol":"makeAsyncResourcesBundleKeys","correct":"import { makeAsyncResourcesBundleKeys } from 'redux-bundler-async-resources'"}],"quickstart":{"code":"import { createSelector } from 'redux-bundler';\nimport { createAsyncResourceBundle } from 'redux-bundler-async-resources';\nimport React from 'react';\n// Assume redux-bundler-hook is installed and configured for useConnect\nimport { useConnect } from 'redux-bundler-hook';\n\n// Mock shopApi for demonstration purposes\nconst shopApi = {\n  fetchHotCarDeals: () => {\n    return new Promise(resolve => {\n      setTimeout(() => {\n        const deals = [{ id: 1, name: 'Sedan Deal', price: 25000 }, { id: 2, name: 'SUV Offer', price: 35000 }];\n        console.log('Fetched hot car deals:', deals);\n        resolve(deals);\n      }, 1500); // Simulate network delay\n    });\n  }\n};\n\n// bundles/hotCarDeals.js\nconst hotCarDealsBundle = {\n  ...createAsyncResourceBundle({\n    name: 'hotCarDeals',\n    staleAfter: 180000, // refresh every 3 minutes\n    expireAfter: 60 * 60000, // delete if not refreshed in an hour\n    getPromise: ({ shopApi: apiContext }) => apiContext.fetchHotCarDeals(),\n  }),\n\n  reactShouldFetchHotCarDeals: createSelector(\n    'selectHotCarDealsIsPendingForFetch',\n    shouldFetch => {\n      if (shouldFetch) {\n        return { actionCreator: 'doFetchHotCarDeals' };\n      }\n    }\n  ),\n};\n\n// Component usage example (HotCarDeals.js)\nconst ErrorMessage = ({ error }) => <div style={{ color: 'red' }}>Error: {error?.message}</div>;\nconst Spinner = () => <div>Loading...</div>;\nconst CarDealsList = ({ deals }) => (\n  <div>\n    <h3>Hot Car Deals</h3>\n    <ul>\n      {deals.map(deal => (\n        <li key={deal.id}>{deal.name}: ${deal.price}</li>\n      ))}\n    </ul>\n  </div>\n);\n\nexport default function HotCarDealsComponent() {\n  // In a real app, 'shopApi' would be part of your main bundle's context\n  const { hotCarDeals, hotCarDealsError } = useConnect(\n      'selectHotCarDeals',\n      'selectHotCarDealsError'\n  );\n\n  if (!hotCarDeals && hotCarDealsError) {\n    return <ErrorMessage error={hotCarDealsError} />;\n  }\n\n  if (!hotCarDeals) {\n    return <Spinner />;\n  }\n\n  return <CarDealsList deals={hotCarDeals} />;\n}\n\n// To run this:\n// 1. Create a root bundler (e.g., composeBundles(hotCarDealsBundle, { getShopApi: () => shopApi }))\n// 2. Wrap your app in <BundlerProvider bundler={store} />\n// 3. Render <HotCarDealsComponent />","lang":"javascript","description":"Demonstrates how to define an async resource bundle for fetching and managing a single list of hot car deals, integrating it with a React component using `redux-bundler-hook`."},"warnings":[{"fix":"Review usage of `createAsyncResourceBundle` and associated selectors/actions for any unexpected behavior or performance changes. Consult the changelog for details if available.","message":"Version 1.1.0 introduced a reimplementation of `createAsyncResourceBundle`. While aiming for 'same naming conventions, implementation logic, and same extra features', internal behavior or edge cases might have changed. Users upgrading from versions prior to 1.1.0 should thoroughly test their existing bundles.","severity":"breaking","affected_versions":">=1.1.0"},{"fix":"Carefully configure `staleAfter` for desired background refresh frequency and `expireAfter` for explicit cache invalidation. Set `Infinity` to disable either mechanism.","message":"Misunderstanding `staleAfter` vs. `expireAfter` can lead to unexpected data persistence or removal. `staleAfter` marks data for refresh but keeps it, while `expireAfter` completely removes it from the store if not refreshed.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Ensure `dependencyKey` selectors return stable, memoized values. Avoid using selectors that return new object/array instances on every render unless explicit re-fetching is desired.","message":"The `dependencyKey` mechanism introduced in v1.2.0 will force-clear a resource bundle when its associated selector's value changes. If the dependency selector frequently changes for non-material reasons, it can lead to excessive re-fetching.","severity":"gotcha","affected_versions":">=1.2.0"},{"fix":"Always ensure compatible versions of `redux-bundler-async-resources` and `redux-bundler-async-resources-hooks`. Refer to the respective package release notes for compatibility guidance.","message":"Integration with `redux-bundler-async-resources-hooks` (especially for `v1.2.1`) requires specific versions. Mismatched versions between the two packages can lead to unexpected behavior or runtime errors.","severity":"gotcha","affected_versions":">=1.2.1"},{"fix":"Ensure all necessary dependencies for `getPromise` are injected into the bundler's context via the `composeBundles` configuration.","message":"The `getPromise` function requires context parameters (e.g., `shopApi`). If these are not provided to your `redux-bundler` store (e.g., through `composeBundles({ getShopApi: () => myApi })`), the promise will fail to execute or throw an error.","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-21T00:00:00.000Z","next_check":"2026-07-20T00:00:00.000Z","problems":[{"fix":"Pass the necessary API client or service into `composeBundles` when initializing the store, e.g., `composeBundles(myBundle, { getShopApi: () => shopApiInstance })`.","cause":"The `shopApi` object or similar dependency required by `getPromise` was not injected into the `redux-bundler` context.","error":"TypeError: Cannot read properties of undefined (reading 'fetchHotCarDeals')"},{"fix":"Update imports to use ES Modules syntax: `import { createAsyncResourceBundle } from 'redux-bundler-async-resources';`.","cause":"Attempting to use CommonJS `require()` syntax in an ESM-only context or a mixed environment not properly configured for CommonJS.","error":"ReferenceError: require is not defined"},{"fix":"Verify the `name` property passed to `createAsyncResourceBundle` exactly matches the base name used in selectors (e.g., 'hotCarDeals' for `selectHotCarDealsIsPendingForFetch`). Ensure the bundle is included in your `composeBundles` call.","cause":"The `name` option in `createAsyncResourceBundle` does not match the expected selector name used in `createSelector` or `useConnect`, or the bundle itself is not correctly added to the root bundler.","error":"Error: A selector 'selectMyResourceNameIsPendingForFetch' could not be found. Check your bundle definition."}],"ecosystem":"npm"}