At commit f292047, react-bits has 5,406 analysed functions, 457 of which score in the critical band. The top of that list is dominated by two distinct risk profiles: BackgroundStudio in src/tools/background-studio/BackgroundStudio.jsx carries an activity-weighted risk score of 18.05 and was touched 1 time in the last 30 days — it is both structurally complex and actively changing, which makes it a live regression risk rather than a cleanup backlog item. Below it, three near-identical copies of ShapeGrid have sat untouched for 42 days with identical critical-band scores, representing structural debt that will hit hard the moment any contributor opens one of those files. I would start with BackgroundStudio this week and schedule the ShapeGrid trio for the next planned refactoring sprint.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
BackgroundStudio | src/tools/background-studio/BackgroundStudio.jsx | 18.0 | 16 | 7 | 60 |
computeNegativeSpaces | src/tools/shape-magic/svgRenderers.js | 17.2 | 65 | 7 | 4 |
ShapeGrid | src/content/Backgrounds/ShapeGrid/ShapeGrid.jsx | 16.9 | 23 | 6 | 43 |
ShapeGrid | src/tailwind/Backgrounds/ShapeGrid/ShapeGrid.jsx | 16.9 | 23 | 6 | 43 |
ShapeGrid | src/ts-default/Backgrounds/ShapeGrid/ShapeGrid.tsx | 16.9 | 23 | 6 | 43 |
Large Repo Analysis
react-bits is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.
Repo-wide risk distribution
5,406 functions analyzed
The distribution tells a clear story: the vast majority of functions (3,580) are low-risk and dormant, but 190 are in the “fire” quadrant — structurally complex and actively changing right now. The 921 functions in the debt quadrant are the slow-burn concern: high structural complexity that hasn’t been touched recently, but will become a regression risk the moment development resumes on those areas.
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.Long Function×10Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.God Function×9God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Complex Branching×7Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Deeply Nested×6Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
Across the top hotspots, the most common antipatterns are exit-heavy functions (10), long functions (10), and god functions (9). These often appear together: a function that does too much tends to accumulate branching paths, each of which needs its own exit condition. That combination directly inflates both cyclomatic complexity and test-coverage burden.
BackgroundStudio — BackgroundStudio.jsx
BackgroundStudio is the top-ranked function in this analysis with an activity-weighted risk score of 18.05, and its quadrant is “fire”: it was touched 1 time in the last 30 days and was last modified 15 days ago. That combination makes it a live regression risk today, not a backlog item.
The source excerpt makes the structural problem concrete. The function opens with at least six useRef calls managing recording state (a mediaRecorderRef, recordingAnimationRef, recordingIntervalRef, recordingTimeoutRef, and a debounceTimer), three useState hooks for recording progress and mobile controls, and a cascade of useMemo derivations that layer URL search params onto background defaults onto local override props. That layered prop-resolution chain — customProps parsed from URL params, merged into baseProps from defaults, merged again into a final props object — is where cyclomatic complexity of 16 accumulates. Each propDef.type branch in the URL-parsing loop (number, boolean, colorArray, color, rgbArray, fallback) is an independent execution path and a required test case.
The fan-out of 60 is the metric I’d flag most urgently. In JavaScript, where dynamic property access and callback chains can obscure static dependencies, a fan-out of 60 means the static count is likely a floor, not a ceiling. A change to how updateProp debounces URL writes, for example, could silently affect anything downstream that reads those search params. The max nesting depth of 7 compounds this: reasoning about the recording state machine while tracking the prop-merge waterfall requires holding a lot of context simultaneously.
Half of the file’s 2 total commits are tagged as bug fixes, which is supporting context for prioritisation — it doesn’t prove the current code is broken, but it does suggest this file has required correction before.
Recommendation: Extract the URL search-param parsing into a dedicated useBackgroundParams hook. That single step removes the multi-branch type-coercion loop from the render function, cuts the cyclomatic complexity roughly in half, and makes the parsing logic independently testable. The recording state machine (the six refs) is a second extraction candidate — a useRecording hook would isolate that concern entirely.
computeNegativeSpaces — svgRenderers.js
computeNegativeSpaces is the most structurally extreme function in this analysis by cyclomatic complexity. A CC of 65 means 65 independent execution paths through a single function — that is not moderate complexity, that is high by any practical standard, and each path is a required test case for correct SVG output.
The source excerpt shows why: the function iterates over a bridges array, and for each bridge dispatches on bridge.rotation via a switch statement with cases for 0, 90, 180, and 270 degrees. Inside each case, there is a nested loop over shapes with compound spatial predicates (s.x < nsRight && s.x + s.w > nsLeft && ...). The nesting depth of 7 comes from the combination of the outer for loop, the switch, and the inner for loop with multi-condition if branches. That structure makes it very hard to reason about what a change to one case’s boundary condition does to the others.
The fan-out of 4 is notably low — this function is largely self-contained, which is the one piece of good news. Changes here are unlikely to ripple outward through the call graph. The risk is inward: getting the spatial geometry right across all four rotation cases, with the correct boundary-shrinking logic for each, is the kind of problem where a subtle off-by-one in one case block won’t be caught until a very specific bridge rotation is exercised.
This file also has half its commits tagged as bug fixes across its 2 commits, consistent with the BackgroundStudio file. Again, that’s historical context, not proof of a current defect.
Recommendation: Decompose the switch into four named helper functions — computeNegativeSpaceRotation0, ...90, ...180, ...270 — each accepting the same shape-bounds arguments. This brings each rotation’s logic to a CC of roughly 10–15 and makes it independently testable with rotation-specific fixtures. The outer iteration loop stays in computeNegativeSpaces as a thin coordinator.
ShapeGrid — three copies across content, tailwind, and ts-default variants
The third, fourth, and fifth positions in the top hotspots table are occupied by three copies of the same ShapeGrid component: src/content/Backgrounds/ShapeGrid/ShapeGrid.jsx, src/tailwind/Backgrounds/ShapeGrid/ShapeGrid.jsx, and src/ts-default/Backgrounds/ShapeGrid/ShapeGrid.tsx. A fourth copy exists in src/ts-tailwind/Backgrounds/ShapeGrid/ShapeGrid.tsx (visible in the context data). All four carry identical scores.
Every one of these functions is in the debt quadrant: 0 touches in the last 30 days, last modified 42 days ago. I want to be precise about what that means: these are not actively changing right now. The risk is that when any one of them is next touched — to add a new shape type, fix a rendering edge case, or update the animation loop — the engineer doing it will be working inside a CC-23 function with nesting depth 6 and fan-out 43, without a recent mental model of the code.
The source excerpt shows a large useEffect that encloses all canvas setup: resize handling, shape dispatch (isHex, isTri, fallback to square/circle), and a drawGrid function that branches further on each shape type. Nested draw helpers (drawHex, drawCircle, drawTriangle) are defined inside the effect, which means they are recreated on every effect run. The hoverTrailAmount and trailCells ref logic adds a further stateful layer managing opacity decay per cell. With a max nesting depth of 6, the deepest paths go: useEffect → drawGrid → isHex branch → inner column/row loop → per-cell hover check.
The duplication itself is the highest-priority concern. Any bug found in one copy — say, a missing if (!ctx) return guard (which, notably, the content variant lacks where the tailwind and TypeScript variants have added it) — requires a coordinated fix across all four files. That is exactly the kind of shotgun-edit scenario that introduces inconsistency under time pressure.
No bug-linked commits, reverts, or hotfix commits appear in the external signals for any of these files. There is no historical defect signal here — this is purely a maintainability and blast-radius concern.
Recommendation: Extract the canvas animation logic into a single useShapeGrid hook in a shared location, then have all four variant components consume it. The variants can retain their own prop signatures and styling wrappers while delegating all canvas state and drawing to one implementation. This doesn’t need to happen in a single PR — start with the TypeScript default variant as the canonical source since it has the strictest typing, then replace the others one at a time.
It is also worth noting that Shuffle in src/tailwind/TextAnimations/Shuffle/Shuffle.jsx (visible in the context data) sits just below the top 5 with a CC of 37 and a fan-out of 60, also in the debt quadrant and untouched for 42 days. It would be the next stop after addressing the ShapeGrid copies. Similarly, TextureLab and Controls in the src/tools/texture-lab/ directory are both fire-quadrant functions touched in the last 15 days — worth watching alongside BackgroundStudio given they share the same tools layer.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
exit_heavy | 10 |
long_function | 10 |
god_function | 9 |
complex_branching | 7 |
deeply_nested | 6 |
These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
Reproduce This Analysis
git clone https://github.com/DavidHDev/react-bits
cd react-bits
git checkout f29204770d77ccc226121cc9eb2ca5775aa9d71d
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →