{"version":3,"file":"useCachedResponsivePrecommitValue.mjs","names":[],"sources":["../src/useCachedResponsivePrecommitValue.ts"],"sourcesContent":["'use strict';\n\nimport type { ItemCleanupPair } from '@isograph/disposable-types';\nimport { useEffect, useRef, useState } from 'react';\nimport type { ParentCache } from './ParentCache';\n\n/**\n * useCachedResponsivePrecommitValue<T>\n * - Takes a mutable parent cache, a factory function, and an onCommit callback.\n * - Returns T before commit after every parent cache change, and null afterward.\n * - Calls onCommit with the ItemCleanupPair during commit after every parent cache change.\n * - The T from the render phase is only temporarily retained. It may have been\n *   disposed by the time of the commit. If so, this hook checks the parent cache\n *   for another T or creates one, and passes this T to onCommit.\n * - If the T returned during the last render is not the same as the one that\n *   is passed to onCommit, during the commit phase, it will schedule another render.\n *\n * Invariant: the returned T has not been disposed during the tick of the render.\n * The T passed to the onCommit callback has not been disposed when the onCommit\n * callback is called.\n *\n * Passing a different parentCache:\n * - Pre-commit, passing a different parentCache has the effect of \"resetting\" this\n *   hook's state to the new cache's state. For example, if you have a cache associated\n *   with a set of variables (e.g. {name: \"Matthew\"}), and pass in another cache\n *   (e.g. associated with {name: \"James\"}), which is empty, the hook will fill that\n *   new cache with the factory function.\n * - Post-commit, passing a different parentCache will reset hook to the pre-commit\n *   state. The cache will return T before commit, then fill the new cache with the\n *   factory function and return null afterwards.\n *\n * Passing a different factory:\n * - Passing a different factory has no effect, except when factory is called,\n *   which is when the parent cache is being filled, or during commit.\n *\n * Passing a different onCommit:\n * - Passing a different onCommit has no effect, except for during commit.\n */\nexport function useCachedResponsivePrecommitValue<T>(\n  parentCache: ParentCache<T>,\n  onCommit: (pair: ItemCleanupPair<T>) => void,\n): { state: T } | null {\n  // TODO: there should be two APIs. One in which we always re-render if the\n  // committed item was not returned during the last render, and one in which\n  // we do not. The latter is useful for cases where every disposable item\n  // behaves identically, but must be loaded.\n  //\n  // This hook is the former, i.e. re-renders if the committed item has changed.\n  const [, rerender] = useState<{} | null>(null);\n  const lastCommittedParentCache = useRef<ParentCache<T> | null>(null);\n\n  useEffect(() => {\n    lastCommittedParentCache.current = parentCache;\n    // On commit, cacheItem may be disposed, because during the render phase,\n    // we only temporarily retained the item, and the temporary retain could have\n    // expired by the time of the commit.\n    //\n    // So, we can be in one of two states:\n    // - the item is not disposed. In that case, permanently retain and use that item.\n    // - the item is disposed. In that case, we can be in two states:\n    //   - the parent cache is not empty (due to another component rendering, or\n    //     another render of the same component.) In that case, permanently retain and\n    //     use the item from the parent cache. (Note: any item present in the parent\n    //     cache is not disposed.)\n    //   - the parent cache is empty. In that case, call factory, getting a new item\n    //     and a cleanup function.\n    //\n    // After the above, we have a non-disposed item and a cleanup function, which we\n    // can pass to onCommit.\n    const undisposedPair = cacheItem.permanentRetainIfNotDisposed(\n      disposeOfTemporaryRetain,\n    );\n    if (undisposedPair != null) {\n      onCommit(undisposedPair);\n    } else {\n      // The cache item we created during render has been disposed. Check if the parent\n      // cache is populated.\n      const existingCacheItemCleanupPair =\n        parentCache.getAndPermanentRetainIfPresent();\n      if (existingCacheItemCleanupPair != null) {\n        onCommit(existingCacheItemCleanupPair);\n      } else {\n        // We did not find an item in the parent cache, create a new one.\n        onCommit(parentCache.factory());\n      }\n      // TODO: Consider whether we always want to rerender if the committed item\n      // was not returned during the last render, or whether some callers will\n      // prefer opting out of this behavior (e.g. if every disposable item behaves\n      // identically, but must be loaded.)\n      rerender({});\n    }\n  }, [parentCache]);\n\n  if (lastCommittedParentCache.current === parentCache) {\n    return null;\n  }\n\n  // Safety: item is only safe to use (i.e. guaranteed not to have been disposed)\n  // during this tick.\n  const [cacheItem, item, disposeOfTemporaryRetain] =\n    parentCache.getOrPopulateAndTemporaryRetain();\n\n  return { state: item };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,kCACd,aACA,UACqB;CAOrB,MAAM,GAAG,YAAY,SAAoB,KAAK;CAC9C,MAAM,2BAA2B,OAA8B,KAAK;AAEpE,iBAAgB;AACd,2BAAyB,UAAU;EAiBnC,MAAM,iBAAiB,UAAU,6BAC/B,yBACD;AACD,MAAI,kBAAkB,KACpB,UAAS,eAAe;OACnB;GAGL,MAAM,+BACJ,YAAY,gCAAgC;AAC9C,OAAI,gCAAgC,KAClC,UAAS,6BAA6B;OAGtC,UAAS,YAAY,SAAS,CAAC;AAMjC,YAAS,EAAE,CAAC;;IAEb,CAAC,YAAY,CAAC;AAEjB,KAAI,yBAAyB,YAAY,YACvC,QAAO;CAKT,MAAM,CAAC,WAAW,MAAM,4BACtB,YAAY,iCAAiC;AAE/C,QAAO,EAAE,OAAO,MAAM"}