Repository navigation
fix: getKeplerFactory number of hook calls errors - #354
Conversation
Signed-off-by: Ilya Boyandin <[email protected]>
📝 WalkthroughWalkthroughReplaced per-render hook memoization with persistent WeakMap caches to stabilize resolved Kepler component wrappers across renders. Exposed a new Changes
Sequence Diagram(s)(Skipped — changes are internal caching and API surface; no multi-component runtime flow requiring a diagram.) Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 🧹 Recent nitpick comments
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/kepler/src/components/KeplerInjector.tsx (2)
165-187:⚠️ Potential issue | 🟠 Major
Wrappedis recreated on every call — callers inside render will trigger remounts.The inner
Componentis correctly cached in the WeakMap, butgetKeplerFactoryreturns a newWrappedfunction (i.e. a new React component type) on every invocation. If a caller happens to invokegetKeplerFactory(SomeFactory)during a component's render (rather than at module scope), React will see a different component type each render and unmount/remount the subtree.Consider also caching the
Wrappedwrapper in the samebyFactorymap (or a parallel cache) keyed by(injector, factory), so repeated calls for the same factory return a referentially stable component.♻️ Sketch: cache the wrapper alongside the resolved component
+const wrapperCache = new WeakMap< + object, + Map<KeplerFactory, React.ComponentType<any>> +>(); + export function getKeplerFactory<TFactory extends KeplerFactory>( factory: TFactory, ): ReturnType<TFactory> { - const Wrapped = ((props: Record<string, unknown>) => { + const injectorInstance = getKeplerInjector(); + let byFactory = wrapperCache.get(injectorInstance as object); + if (byFactory === undefined) { + byFactory = new Map(); + wrapperCache.set(injectorInstance as object, byFactory); + } + let Wrapped = byFactory.get(factory); + if (Wrapped !== undefined) { + return Wrapped as unknown as ReturnType<TFactory>; + } + Wrapped = ((props: Record<string, unknown>) => { const injectorInstance = getKeplerInjector(); let byFactory = injectorToFactoryCache.get(injectorInstance as object); ... return <Component {...props} />; - }) as unknown as ReturnType<TFactory>; - return Wrapped; + }) as React.ComponentType<any>; + byFactory.set(factory, Wrapped); + return Wrapped as unknown as ReturnType<TFactory>; }
189-193:⚠️ Potential issue | 🟡 MinorAdd
KeplerInjectorto barrel export if intended as public API.
KeplerInjectoris exported frompackages/kepler/src/components/KeplerInjector.tsxbut not re-exported frompackages/kepler/src/index.ts. IfKeplerInjectoris meant to be the stable public entry point, add it to the barrel export alongside the existing re-exports (getKeplerFactory,getKeplerInjector, etc.).Consider adding JSDoc to
KeplerInjector.get()noting that it should be called at module scope or in a constant initializer (outside component render) to keep the returned component reference stable.
🧹 Nitpick comments (1)
packages/kepler/src/components/KeplerInjector.tsx (1)
170-183: Mutating a cache during render is a side-effect — acceptable here but worth a comment.Populating
injectorToFactoryCacheinside the render body is technically a side effect. It works because the write is idempotent and the result is deterministic for a given(injector, factory)pair, so it's safe under React 18 strict mode's double-invoke semantics. The existing comment on lines 158-159 partially explains this, but a brief inline note at the mutation site (e.g., "Idempotent lazy init — safe under concurrent/strict mode") would help future readers.
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/kepler/src/components/KeplerInjector.tsx (1)
187-191:⚠️ Potential issue | 🟡 MinorAdd
KeplerInjectorto the package index exports.
KeplerInjectoris exported frompackages/kepler/src/components/KeplerInjector.tsxbut not re-exported frompackages/kepler/src/index.ts. If it is intended as a public API, add it to the export list alongside the related function exports.
🤖 Fix all issues with AI agents
In `@packages/kepler/src/components/KeplerInjector.tsx`:
- Around line 158-185: The wrapper component returned by getKeplerFactory is
anonymous which hinders React DevTools; after creating Wrapped (the per-factory
cached component that renders Component obtained from injectorInstance.get), set
a descriptive displayName on Wrapped (e.g., using Component.displayName ||
Component.name combined with a prefix like "KeplerInjector(") before caching it
in the Map stored on injectorToFactoryCache so the cached Wrapped has a readable
displayName for debugging.
Signed-off-by: Ilya Boyandin <[email protected]>
…ponent from the current injector during render. Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Summary by CodeRabbit
New Features
Performance Improvements