react-bits' tools and backgrounds carry the highest activity risk — 5 functions to address first

Hotspots analysis of DavidHDev/react-bits at f292047 finds two live-fire functions in the tools layer and three identical ShapeGrid copies in structural debt — all rated critical band.

Stephen Collins ·
Generated by hotspots · free & open source
Activity Risk18.05Low
Hottest FunctionBackgroundStudio

Antipatterns Detected

exit_heavy10long_function10god_function9complex_branching7deeply_nested6

Run this on your own codebase

Hotspots runs locally in under a minute — no account, no data leaves your machine.

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is a god function and why does it matter in react-bits?

A god function is a single function that takes on too many distinct responsibilities — managing state, coordinating side effects, parsing inputs, and driving rendering — rather than delegating to focused helpers. The concrete problem is coupling: because the function calls so many other functions (fan-out) and handles so many conditions (cyclomatic complexity), a change to any one responsibility risks breaking another, and testing it in isolation requires setting up the entire context it depends on. In this analysis, 9 of the top hotspot functions carry the god_function pattern, with `BackgroundStudio` managing URL parameter parsing, mobile layout, debounced prop updates, and a video recording state machine all in one place. That breadth is what drives its fan-out to 60 and makes each change to it a broad-surface regression risk.

How do I reduce cyclomatic complexity in JavaScript?

The most direct technique is decompose-conditional: identify each major branch cluster and extract it into a named function with a clear input/output contract. A cyclomatic complexity above 15 is a practical signal to start splitting; above 30, splitting is urgent because the number of test cases required to cover every path is already unmanageable. For `computeNegativeSpaces` specifically — with a CC of 65 — I would start by extracting each `case` block of the rotation `switch` into its own function. That alone should bring the parent function below CC 10 and make each rotation case independently testable with a small set of spatial fixtures. In JavaScript, where there is no compiler enforcing the boundary, naming the extracted functions descriptively is the main mechanism for making the decomposition legible to the next contributor.

Is react-bits actively maintained?

Yes — the fire-quadrant functions in this analysis are direct evidence of active development. `BackgroundStudio` was touched 1 time in the last 30 days and was last modified 15 days ago; `computeNegativeSpaces` matches exactly. Several context functions — `ComponentList` with 2 touches in 30 days, and `TextureLab` and `Controls` each with 1 — confirm that the tools layer is under active development right now. The three `ShapeGrid` copies, by contrast, have had 0 touches in 30 days and were last changed 42 days ago, which is a normal state for stable background components. Active development and accumulated structural complexity are not mutually exclusive, and nothing in this data suggests the project is abandoned or declining.

How do I reproduce this analysis?

The Hotspots CLI is available at github.com/hotspots-dev/hotspots. This analysis was run against commit f292047 of DavidHDev/react-bits. After checking out that commit with `git checkout f292047`, run `hotspots analyze . --mode snapshot --explain-patterns --force` from the repository root to reproduce the full results. The same command works on any local git repository without any configuration file.

What does activity-weighted risk mean?

Activity-weighted risk combines a structural complexity sub-score — derived from cyclomatic complexity, nesting depth, and fan-out — with recent commit frequency, so functions that are both hard to understand and actively changing score the highest. A function with a cyclomatic complexity of 65 that hasn't been touched in two years scores meaningfully lower than one with a cyclomatic complexity of 16 touched last week, because the dormant function has lower near-term regression probability even though it looks more complicated in isolation. The practical implication is that the score tells you where refactoring effort reduces the probability of a bug being introduced right now — not just where the code is abstractly complex. That is why `BackgroundStudio`, despite having a lower cyclomatic complexity than `computeNegativeSpaces`, scores higher overall: its recent activity makes its complexity an active liability.

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

FunctionFileRiskCCNDFO
BackgroundStudiosrc/tools/background-studio/BackgroundStudio.jsx18.016760
computeNegativeSpacessrc/tools/shape-magic/svgRenderers.js17.26574
ShapeGridsrc/content/Backgrounds/ShapeGrid/ShapeGrid.jsx16.923643
ShapeGridsrc/tailwind/Backgrounds/ShapeGrid/ShapeGrid.jsx16.923643
ShapeGridsrc/ts-default/Backgrounds/ShapeGrid/ShapeGrid.tsx16.923643

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

Triage Band Distribution
Fire190Debt921Watch715OK3580

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.

Detected Antipatterns
Exit Heavy×10Exit Heavy
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
src/tools/background-studio/BackgroundStudio.jsx
18.05
fire
CC 16
ND 7
FO 60
touches/30d 1

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.

Fan-out (distinct callees) 60
threshold: 15

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
src/tools/shape-magic/svgRenderers.js
17.17
fire
CC 65
ND 7
FO 4
touches/30d 1

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.

Cyclomatic Complexity 65
threshold: 10

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.

ShapeGrid
src/content/Backgrounds/ShapeGrid/ShapeGrid.jsx
16.86
debt
CC 23
ND 6
FO 43
touches/30d 0

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: useEffectdrawGridisHex branch → inner column/row loop → per-cell hover check.

Fan-out (distinct callees) 43
threshold: 15

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:

PatternOccurrences
exit_heavy10
long_function10
god_function9
complex_branching7
deeply_nested6

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 →

Related Analyses