mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-10-30 15:19:21 +00:00
* refactor: reduce useLoadFeature re-renders
Three targeted optimizations to cut unnecessary re-renders during
feature loading:
1. Replace 3 separate useState calls in useAsync (packages/utils) with
a single useReducer. Each state transition is now one dispatch = one
render, eliminating double-renders from setData + setLoading firing
in separate microtasks on promise resolution.
2. Cache loaded feature module in a ref inside useLoadFeature. Survives
isEnabled flicker during chain switches (true -> undefined -> true)
by returning the cached module from a resolved promise instead of
re-running import(). Feature modules are static so re-fetching is
pure waste.
3. Stabilize the stub proxy reference in useLoadFeature. The Proxy is
created once per hook instance and reads meta values ($isLoading,
$isDisabled, etc.) from a ref. The object reference stays the same
across all not-ready states, so children receiving the feature object
don't re-render on meta changes alone.
* refactor: eliminate $isLoading from useLoadFeature
Remove the intermediate loading state from the feature loading lifecycle.
Features now transition directly from not-ready to ready in a single
render, reducing unnecessary re-renders. Replace useAsync with a simpler
useEffect + useState that only triggers one state update.
The useAsync optimizations (useReducer) are kept in packages/utils as
they benefit all consumers.
* refactor: reduce useLoadFeature cyclomatic complexity to 9
Extract getFeature, getError, and toError helpers to move branch
points out of the main hook function. Simplify the final useMemo
condition since $isReady is derived from !!feature.
* refactor: add shared feature registry to useLoadFeature
Replace per-instance cachedFeatureRef with a module-level registry
(featureCache Map) shared across all hook instances. When multiple
components call useLoadFeature(SameFeature), only the first triggers
a load — subsequent components get the result synchronously via the
useState initializer (1 render instead of 2).
Also deduplicates concurrent load() calls via a pendingLoads Map.
Errors are not cached globally so retry is possible on remount.
* revert: undo useAsync useReducer optimization
The useReducer replacement caused an infinite render loop when
asyncCall returns undefined — dispatch({ type: 'reset' }) always
creates a new state object reference, unlike the original useState
calls which bail out on same primitive values. This caused OOM
crashes in useAddressActivity tests.
The useLoadFeature optimizations (shared registry, stable proxy,
eliminated $isLoading) are independent and remain in place.
---------
Co-authored-by: Hanzo Dev <[email protected]>