@wazir-dev/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +111 -0
- package/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +101 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/assets/composition-engine.mmd +34 -0
- package/assets/demo-script.sh +17 -0
- package/assets/logo-dark.svg +14 -0
- package/assets/logo.svg +14 -0
- package/assets/pipeline.mmd +39 -0
- package/assets/record-demo.sh +51 -0
- package/docs/README.md +51 -0
- package/docs/adapters/context-mode.md +60 -0
- package/docs/concepts/architecture.md +87 -0
- package/docs/concepts/artifact-model.md +60 -0
- package/docs/concepts/composition-engine.md +36 -0
- package/docs/concepts/indexing-and-recall.md +160 -0
- package/docs/concepts/observability.md +41 -0
- package/docs/concepts/roles-and-workflows.md +59 -0
- package/docs/concepts/terminology-policy.md +27 -0
- package/docs/getting-started/01-installation.md +78 -0
- package/docs/getting-started/02-first-run.md +102 -0
- package/docs/getting-started/03-adding-to-project.md +15 -0
- package/docs/getting-started/04-host-setup.md +15 -0
- package/docs/guides/ci-integration.md +15 -0
- package/docs/guides/creating-skills.md +15 -0
- package/docs/guides/expertise-module-authoring.md +15 -0
- package/docs/guides/hook-development.md +15 -0
- package/docs/guides/memory-and-learnings.md +34 -0
- package/docs/guides/multi-host-export.md +15 -0
- package/docs/guides/troubleshooting.md +101 -0
- package/docs/guides/writing-custom-roles.md +15 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-design.md +592 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-plan.md +598 -0
- package/docs/plans/2026-03-15-docs-enforcement-plan.md +238 -0
- package/docs/readmes/INDEX.md +99 -0
- package/docs/readmes/features/expertise/README.md +171 -0
- package/docs/readmes/features/exports/README.md +222 -0
- package/docs/readmes/features/hooks/README.md +103 -0
- package/docs/readmes/features/hooks/loop-cap-guard.md +133 -0
- package/docs/readmes/features/hooks/post-tool-capture.md +121 -0
- package/docs/readmes/features/hooks/post-tool-lint.md +130 -0
- package/docs/readmes/features/hooks/pre-compact-summary.md +122 -0
- package/docs/readmes/features/hooks/pre-tool-capture-route.md +100 -0
- package/docs/readmes/features/hooks/protected-path-write-guard.md +128 -0
- package/docs/readmes/features/hooks/session-start.md +119 -0
- package/docs/readmes/features/hooks/stop-handoff-harvest.md +125 -0
- package/docs/readmes/features/roles/README.md +157 -0
- package/docs/readmes/features/roles/clarifier.md +152 -0
- package/docs/readmes/features/roles/content-author.md +190 -0
- package/docs/readmes/features/roles/designer.md +193 -0
- package/docs/readmes/features/roles/executor.md +184 -0
- package/docs/readmes/features/roles/learner.md +210 -0
- package/docs/readmes/features/roles/planner.md +182 -0
- package/docs/readmes/features/roles/researcher.md +164 -0
- package/docs/readmes/features/roles/reviewer.md +184 -0
- package/docs/readmes/features/roles/specifier.md +162 -0
- package/docs/readmes/features/roles/verifier.md +215 -0
- package/docs/readmes/features/schemas/README.md +178 -0
- package/docs/readmes/features/skills/README.md +63 -0
- package/docs/readmes/features/skills/brainstorming.md +96 -0
- package/docs/readmes/features/skills/debugging.md +148 -0
- package/docs/readmes/features/skills/design.md +120 -0
- package/docs/readmes/features/skills/prepare-next.md +109 -0
- package/docs/readmes/features/skills/run-audit.md +159 -0
- package/docs/readmes/features/skills/scan-project.md +109 -0
- package/docs/readmes/features/skills/self-audit.md +176 -0
- package/docs/readmes/features/skills/tdd.md +137 -0
- package/docs/readmes/features/skills/using-skills.md +92 -0
- package/docs/readmes/features/skills/verification.md +120 -0
- package/docs/readmes/features/skills/writing-plans.md +104 -0
- package/docs/readmes/features/tooling/README.md +320 -0
- package/docs/readmes/features/workflows/README.md +186 -0
- package/docs/readmes/features/workflows/author.md +181 -0
- package/docs/readmes/features/workflows/clarify.md +154 -0
- package/docs/readmes/features/workflows/design-review.md +171 -0
- package/docs/readmes/features/workflows/design.md +169 -0
- package/docs/readmes/features/workflows/discover.md +162 -0
- package/docs/readmes/features/workflows/execute.md +173 -0
- package/docs/readmes/features/workflows/learn.md +167 -0
- package/docs/readmes/features/workflows/plan-review.md +165 -0
- package/docs/readmes/features/workflows/plan.md +170 -0
- package/docs/readmes/features/workflows/prepare-next.md +167 -0
- package/docs/readmes/features/workflows/review.md +169 -0
- package/docs/readmes/features/workflows/run-audit.md +191 -0
- package/docs/readmes/features/workflows/spec-challenge.md +159 -0
- package/docs/readmes/features/workflows/specify.md +160 -0
- package/docs/readmes/features/workflows/verify.md +177 -0
- package/docs/readmes/packages/README.md +50 -0
- package/docs/readmes/packages/ajv.md +117 -0
- package/docs/readmes/packages/context-mode.md +118 -0
- package/docs/readmes/packages/gray-matter.md +116 -0
- package/docs/readmes/packages/node-test.md +137 -0
- package/docs/readmes/packages/yaml.md +112 -0
- package/docs/reference/configuration-reference.md +159 -0
- package/docs/reference/expertise-index.md +52 -0
- package/docs/reference/git-flow.md +43 -0
- package/docs/reference/hooks.md +87 -0
- package/docs/reference/host-exports.md +50 -0
- package/docs/reference/launch-checklist.md +172 -0
- package/docs/reference/marketplace-listings.md +76 -0
- package/docs/reference/release-process.md +34 -0
- package/docs/reference/roles-reference.md +77 -0
- package/docs/reference/skills.md +33 -0
- package/docs/reference/templates.md +29 -0
- package/docs/reference/tooling-cli.md +94 -0
- package/docs/truth-claims.yaml +222 -0
- package/expertise/PROGRESS.md +63 -0
- package/expertise/README.md +18 -0
- package/expertise/antipatterns/PROGRESS.md +56 -0
- package/expertise/antipatterns/backend/api-design-antipatterns.md +1271 -0
- package/expertise/antipatterns/backend/auth-antipatterns.md +1195 -0
- package/expertise/antipatterns/backend/caching-antipatterns.md +622 -0
- package/expertise/antipatterns/backend/database-antipatterns.md +1038 -0
- package/expertise/antipatterns/backend/index.md +24 -0
- package/expertise/antipatterns/backend/microservices-antipatterns.md +850 -0
- package/expertise/antipatterns/code/architecture-antipatterns.md +919 -0
- package/expertise/antipatterns/code/async-antipatterns.md +622 -0
- package/expertise/antipatterns/code/code-smells.md +1186 -0
- package/expertise/antipatterns/code/dependency-antipatterns.md +1209 -0
- package/expertise/antipatterns/code/error-handling-antipatterns.md +1360 -0
- package/expertise/antipatterns/code/index.md +27 -0
- package/expertise/antipatterns/code/naming-and-abstraction.md +1118 -0
- package/expertise/antipatterns/code/state-management-antipatterns.md +1076 -0
- package/expertise/antipatterns/code/testing-antipatterns.md +1053 -0
- package/expertise/antipatterns/design/accessibility-antipatterns.md +1136 -0
- package/expertise/antipatterns/design/dark-patterns.md +1121 -0
- package/expertise/antipatterns/design/index.md +22 -0
- package/expertise/antipatterns/design/ui-antipatterns.md +1202 -0
- package/expertise/antipatterns/design/ux-antipatterns.md +680 -0
- package/expertise/antipatterns/frontend/css-layout-antipatterns.md +691 -0
- package/expertise/antipatterns/frontend/flutter-antipatterns.md +1827 -0
- package/expertise/antipatterns/frontend/index.md +23 -0
- package/expertise/antipatterns/frontend/mobile-antipatterns.md +573 -0
- package/expertise/antipatterns/frontend/react-antipatterns.md +1128 -0
- package/expertise/antipatterns/frontend/spa-antipatterns.md +1235 -0
- package/expertise/antipatterns/index.md +31 -0
- package/expertise/antipatterns/performance/index.md +20 -0
- package/expertise/antipatterns/performance/performance-antipatterns.md +1013 -0
- package/expertise/antipatterns/performance/premature-optimization.md +623 -0
- package/expertise/antipatterns/performance/scaling-antipatterns.md +785 -0
- package/expertise/antipatterns/process/ai-coding-antipatterns.md +853 -0
- package/expertise/antipatterns/process/code-review-antipatterns.md +656 -0
- package/expertise/antipatterns/process/deployment-antipatterns.md +920 -0
- package/expertise/antipatterns/process/index.md +23 -0
- package/expertise/antipatterns/process/technical-debt-antipatterns.md +647 -0
- package/expertise/antipatterns/security/index.md +20 -0
- package/expertise/antipatterns/security/secrets-antipatterns.md +849 -0
- package/expertise/antipatterns/security/security-theater.md +843 -0
- package/expertise/antipatterns/security/vulnerability-patterns.md +801 -0
- package/expertise/architecture/PROGRESS.md +70 -0
- package/expertise/architecture/data/caching-architecture.md +671 -0
- package/expertise/architecture/data/data-consistency.md +574 -0
- package/expertise/architecture/data/data-modeling.md +536 -0
- package/expertise/architecture/data/event-streams-and-queues.md +634 -0
- package/expertise/architecture/data/index.md +25 -0
- package/expertise/architecture/data/search-architecture.md +663 -0
- package/expertise/architecture/data/sql-vs-nosql.md +708 -0
- package/expertise/architecture/decisions/architecture-decision-records.md +640 -0
- package/expertise/architecture/decisions/build-vs-buy.md +616 -0
- package/expertise/architecture/decisions/index.md +23 -0
- package/expertise/architecture/decisions/monolith-to-microservices.md +790 -0
- package/expertise/architecture/decisions/technology-selection.md +616 -0
- package/expertise/architecture/distributed/cap-theorem-and-tradeoffs.md +800 -0
- package/expertise/architecture/distributed/circuit-breaker-bulkhead.md +741 -0
- package/expertise/architecture/distributed/consensus-and-coordination.md +796 -0
- package/expertise/architecture/distributed/distributed-systems-fundamentals.md +564 -0
- package/expertise/architecture/distributed/idempotency-and-retry.md +796 -0
- package/expertise/architecture/distributed/index.md +25 -0
- package/expertise/architecture/distributed/saga-pattern.md +797 -0
- package/expertise/architecture/foundations/architectural-thinking.md +460 -0
- package/expertise/architecture/foundations/coupling-and-cohesion.md +770 -0
- package/expertise/architecture/foundations/design-principles-solid.md +649 -0
- package/expertise/architecture/foundations/domain-driven-design.md +719 -0
- package/expertise/architecture/foundations/index.md +25 -0
- package/expertise/architecture/foundations/separation-of-concerns.md +472 -0
- package/expertise/architecture/foundations/twelve-factor-app.md +797 -0
- package/expertise/architecture/index.md +34 -0
- package/expertise/architecture/integration/api-design-graphql.md +638 -0
- package/expertise/architecture/integration/api-design-grpc.md +804 -0
- package/expertise/architecture/integration/api-design-rest.md +892 -0
- package/expertise/architecture/integration/index.md +25 -0
- package/expertise/architecture/integration/third-party-integration.md +795 -0
- package/expertise/architecture/integration/webhooks-and-callbacks.md +1152 -0
- package/expertise/architecture/integration/websockets-realtime.md +791 -0
- package/expertise/architecture/mobile-architecture/index.md +22 -0
- package/expertise/architecture/mobile-architecture/mobile-app-architecture.md +780 -0
- package/expertise/architecture/mobile-architecture/mobile-backend-for-frontend.md +670 -0
- package/expertise/architecture/mobile-architecture/offline-first.md +719 -0
- package/expertise/architecture/mobile-architecture/push-and-sync.md +782 -0
- package/expertise/architecture/patterns/cqrs-event-sourcing.md +717 -0
- package/expertise/architecture/patterns/event-driven.md +797 -0
- package/expertise/architecture/patterns/hexagonal-clean-architecture.md +870 -0
- package/expertise/architecture/patterns/index.md +27 -0
- package/expertise/architecture/patterns/layered-architecture.md +736 -0
- package/expertise/architecture/patterns/microservices.md +753 -0
- package/expertise/architecture/patterns/modular-monolith.md +692 -0
- package/expertise/architecture/patterns/monolith.md +626 -0
- package/expertise/architecture/patterns/plugin-architecture.md +735 -0
- package/expertise/architecture/patterns/serverless.md +780 -0
- package/expertise/architecture/scaling/database-scaling.md +615 -0
- package/expertise/architecture/scaling/feature-flags-and-rollouts.md +757 -0
- package/expertise/architecture/scaling/horizontal-vs-vertical.md +606 -0
- package/expertise/architecture/scaling/index.md +24 -0
- package/expertise/architecture/scaling/multi-tenancy.md +800 -0
- package/expertise/architecture/scaling/stateless-design.md +787 -0
- package/expertise/backend/embedded-firmware.md +625 -0
- package/expertise/backend/go.md +853 -0
- package/expertise/backend/index.md +24 -0
- package/expertise/backend/java-spring.md +448 -0
- package/expertise/backend/node-typescript.md +625 -0
- package/expertise/backend/python-fastapi.md +724 -0
- package/expertise/backend/rust.md +458 -0
- package/expertise/backend/solidity.md +711 -0
- package/expertise/composition-map.yaml +443 -0
- package/expertise/content/foundations/content-modeling.md +395 -0
- package/expertise/content/foundations/editorial-standards.md +449 -0
- package/expertise/content/foundations/index.md +24 -0
- package/expertise/content/foundations/microcopy.md +455 -0
- package/expertise/content/foundations/terminology-governance.md +509 -0
- package/expertise/content/index.md +34 -0
- package/expertise/content/patterns/accessibility-copy.md +518 -0
- package/expertise/content/patterns/index.md +24 -0
- package/expertise/content/patterns/notification-content.md +433 -0
- package/expertise/content/patterns/sample-content.md +486 -0
- package/expertise/content/patterns/state-copy.md +439 -0
- package/expertise/design/PROGRESS.md +58 -0
- package/expertise/design/disciplines/dark-mode-theming.md +577 -0
- package/expertise/design/disciplines/design-systems.md +595 -0
- package/expertise/design/disciplines/index.md +25 -0
- package/expertise/design/disciplines/information-architecture.md +800 -0
- package/expertise/design/disciplines/interaction-design.md +788 -0
- package/expertise/design/disciplines/responsive-design.md +552 -0
- package/expertise/design/disciplines/usability-testing.md +516 -0
- package/expertise/design/disciplines/user-research.md +792 -0
- package/expertise/design/foundations/accessibility-design.md +796 -0
- package/expertise/design/foundations/color-theory.md +797 -0
- package/expertise/design/foundations/iconography.md +795 -0
- package/expertise/design/foundations/index.md +26 -0
- package/expertise/design/foundations/motion-and-animation.md +653 -0
- package/expertise/design/foundations/rtl-design.md +585 -0
- package/expertise/design/foundations/spacing-and-layout.md +607 -0
- package/expertise/design/foundations/typography.md +800 -0
- package/expertise/design/foundations/visual-hierarchy.md +761 -0
- package/expertise/design/index.md +32 -0
- package/expertise/design/patterns/authentication-flows.md +474 -0
- package/expertise/design/patterns/content-consumption.md +789 -0
- package/expertise/design/patterns/data-display.md +618 -0
- package/expertise/design/patterns/e-commerce.md +1494 -0
- package/expertise/design/patterns/feedback-and-states.md +642 -0
- package/expertise/design/patterns/forms-and-input.md +819 -0
- package/expertise/design/patterns/gamification.md +801 -0
- package/expertise/design/patterns/index.md +31 -0
- package/expertise/design/patterns/microinteractions.md +449 -0
- package/expertise/design/patterns/navigation.md +800 -0
- package/expertise/design/patterns/notifications.md +705 -0
- package/expertise/design/patterns/onboarding.md +700 -0
- package/expertise/design/patterns/search-and-filter.md +601 -0
- package/expertise/design/patterns/settings-and-preferences.md +768 -0
- package/expertise/design/patterns/social-and-community.md +748 -0
- package/expertise/design/platforms/desktop-native.md +612 -0
- package/expertise/design/platforms/index.md +25 -0
- package/expertise/design/platforms/mobile-android.md +825 -0
- package/expertise/design/platforms/mobile-cross-platform.md +983 -0
- package/expertise/design/platforms/mobile-ios.md +699 -0
- package/expertise/design/platforms/tablet.md +794 -0
- package/expertise/design/platforms/web-dashboard.md +790 -0
- package/expertise/design/platforms/web-responsive.md +550 -0
- package/expertise/design/psychology/behavioral-nudges.md +449 -0
- package/expertise/design/psychology/cognitive-load.md +1191 -0
- package/expertise/design/psychology/error-psychology.md +778 -0
- package/expertise/design/psychology/index.md +22 -0
- package/expertise/design/psychology/persuasive-design.md +736 -0
- package/expertise/design/psychology/user-mental-models.md +623 -0
- package/expertise/design/tooling/open-pencil.md +266 -0
- package/expertise/frontend/angular.md +1073 -0
- package/expertise/frontend/desktop-electron.md +546 -0
- package/expertise/frontend/flutter.md +782 -0
- package/expertise/frontend/index.md +27 -0
- package/expertise/frontend/native-android.md +409 -0
- package/expertise/frontend/native-ios.md +490 -0
- package/expertise/frontend/react-native.md +1160 -0
- package/expertise/frontend/react.md +808 -0
- package/expertise/frontend/vue.md +1089 -0
- package/expertise/humanize/domain-rules-code.md +79 -0
- package/expertise/humanize/domain-rules-content.md +67 -0
- package/expertise/humanize/domain-rules-technical-docs.md +56 -0
- package/expertise/humanize/index.md +35 -0
- package/expertise/humanize/self-audit-checklist.md +87 -0
- package/expertise/humanize/sentence-patterns.md +218 -0
- package/expertise/humanize/vocabulary-blacklist.md +105 -0
- package/expertise/i18n/PROGRESS.md +65 -0
- package/expertise/i18n/advanced/accessibility-and-i18n.md +28 -0
- package/expertise/i18n/advanced/bidirectional-text-algorithm.md +38 -0
- package/expertise/i18n/advanced/complex-scripts.md +30 -0
- package/expertise/i18n/advanced/performance-and-i18n.md +27 -0
- package/expertise/i18n/advanced/testing-i18n.md +28 -0
- package/expertise/i18n/content/content-adaptation.md +23 -0
- package/expertise/i18n/content/locale-specific-formatting.md +23 -0
- package/expertise/i18n/content/machine-translation-integration.md +28 -0
- package/expertise/i18n/content/translation-management.md +29 -0
- package/expertise/i18n/foundations/date-time-calendars.md +67 -0
- package/expertise/i18n/foundations/i18n-architecture.md +272 -0
- package/expertise/i18n/foundations/locale-and-language-tags.md +79 -0
- package/expertise/i18n/foundations/numbers-currency-units.md +61 -0
- package/expertise/i18n/foundations/pluralization-and-gender.md +109 -0
- package/expertise/i18n/foundations/string-externalization.md +236 -0
- package/expertise/i18n/foundations/text-direction-bidi.md +241 -0
- package/expertise/i18n/foundations/unicode-and-encoding.md +86 -0
- package/expertise/i18n/index.md +38 -0
- package/expertise/i18n/platform/backend-i18n.md +31 -0
- package/expertise/i18n/platform/flutter-i18n.md +148 -0
- package/expertise/i18n/platform/native-android-i18n.md +36 -0
- package/expertise/i18n/platform/native-ios-i18n.md +36 -0
- package/expertise/i18n/platform/react-i18n.md +103 -0
- package/expertise/i18n/platform/web-css-i18n.md +81 -0
- package/expertise/i18n/rtl/arabic-specific.md +175 -0
- package/expertise/i18n/rtl/hebrew-specific.md +149 -0
- package/expertise/i18n/rtl/rtl-animations-and-transitions.md +111 -0
- package/expertise/i18n/rtl/rtl-forms-and-input.md +161 -0
- package/expertise/i18n/rtl/rtl-fundamentals.md +211 -0
- package/expertise/i18n/rtl/rtl-icons-and-images.md +181 -0
- package/expertise/i18n/rtl/rtl-layout-mirroring.md +252 -0
- package/expertise/i18n/rtl/rtl-navigation-and-gestures.md +107 -0
- package/expertise/i18n/rtl/rtl-testing-and-qa.md +147 -0
- package/expertise/i18n/rtl/rtl-typography.md +160 -0
- package/expertise/index.md +113 -0
- package/expertise/index.yaml +216 -0
- package/expertise/infrastructure/cloud-aws.md +597 -0
- package/expertise/infrastructure/cloud-gcp.md +599 -0
- package/expertise/infrastructure/cybersecurity.md +816 -0
- package/expertise/infrastructure/database-mongodb.md +447 -0
- package/expertise/infrastructure/database-postgres.md +400 -0
- package/expertise/infrastructure/devops-cicd.md +787 -0
- package/expertise/infrastructure/index.md +27 -0
- package/expertise/performance/PROGRESS.md +50 -0
- package/expertise/performance/backend/api-latency.md +1204 -0
- package/expertise/performance/backend/background-jobs.md +506 -0
- package/expertise/performance/backend/connection-pooling.md +1209 -0
- package/expertise/performance/backend/database-query-optimization.md +515 -0
- package/expertise/performance/backend/index.md +23 -0
- package/expertise/performance/backend/rate-limiting-and-throttling.md +971 -0
- package/expertise/performance/foundations/algorithmic-complexity.md +954 -0
- package/expertise/performance/foundations/caching-strategies.md +489 -0
- package/expertise/performance/foundations/concurrency-and-parallelism.md +847 -0
- package/expertise/performance/foundations/index.md +24 -0
- package/expertise/performance/foundations/measuring-and-profiling.md +440 -0
- package/expertise/performance/foundations/memory-management.md +964 -0
- package/expertise/performance/foundations/performance-budgets.md +1314 -0
- package/expertise/performance/index.md +31 -0
- package/expertise/performance/infrastructure/auto-scaling.md +1059 -0
- package/expertise/performance/infrastructure/cdn-and-edge.md +1081 -0
- package/expertise/performance/infrastructure/index.md +22 -0
- package/expertise/performance/infrastructure/load-balancing.md +1081 -0
- package/expertise/performance/infrastructure/observability.md +1079 -0
- package/expertise/performance/mobile/index.md +23 -0
- package/expertise/performance/mobile/mobile-animations.md +544 -0
- package/expertise/performance/mobile/mobile-memory-battery.md +416 -0
- package/expertise/performance/mobile/mobile-network.md +452 -0
- package/expertise/performance/mobile/mobile-rendering.md +599 -0
- package/expertise/performance/mobile/mobile-startup-time.md +505 -0
- package/expertise/performance/platform-specific/flutter-performance.md +647 -0
- package/expertise/performance/platform-specific/index.md +22 -0
- package/expertise/performance/platform-specific/node-performance.md +1307 -0
- package/expertise/performance/platform-specific/postgres-performance.md +1366 -0
- package/expertise/performance/platform-specific/react-performance.md +1403 -0
- package/expertise/performance/web/bundle-optimization.md +1239 -0
- package/expertise/performance/web/image-and-media.md +636 -0
- package/expertise/performance/web/index.md +24 -0
- package/expertise/performance/web/network-optimization.md +1133 -0
- package/expertise/performance/web/rendering-performance.md +1098 -0
- package/expertise/performance/web/ssr-and-hydration.md +918 -0
- package/expertise/performance/web/web-vitals.md +1374 -0
- package/expertise/quality/accessibility.md +985 -0
- package/expertise/quality/evidence-based-verification.md +499 -0
- package/expertise/quality/index.md +24 -0
- package/expertise/quality/ml-model-audit.md +614 -0
- package/expertise/quality/performance.md +600 -0
- package/expertise/quality/testing-api.md +891 -0
- package/expertise/quality/testing-mobile.md +496 -0
- package/expertise/quality/testing-web.md +849 -0
- package/expertise/security/PROGRESS.md +54 -0
- package/expertise/security/agentic-identity.md +540 -0
- package/expertise/security/compliance-frameworks.md +601 -0
- package/expertise/security/data/data-encryption.md +364 -0
- package/expertise/security/data/data-privacy-gdpr.md +692 -0
- package/expertise/security/data/database-security.md +1171 -0
- package/expertise/security/data/index.md +22 -0
- package/expertise/security/data/pii-handling.md +531 -0
- package/expertise/security/foundations/authentication.md +1041 -0
- package/expertise/security/foundations/authorization.md +603 -0
- package/expertise/security/foundations/cryptography.md +1001 -0
- package/expertise/security/foundations/index.md +25 -0
- package/expertise/security/foundations/owasp-top-10.md +1354 -0
- package/expertise/security/foundations/secrets-management.md +1217 -0
- package/expertise/security/foundations/secure-sdlc.md +700 -0
- package/expertise/security/foundations/supply-chain-security.md +698 -0
- package/expertise/security/index.md +31 -0
- package/expertise/security/infrastructure/cloud-security-aws.md +1296 -0
- package/expertise/security/infrastructure/cloud-security-gcp.md +1376 -0
- package/expertise/security/infrastructure/container-security.md +721 -0
- package/expertise/security/infrastructure/incident-response.md +1295 -0
- package/expertise/security/infrastructure/index.md +24 -0
- package/expertise/security/infrastructure/logging-and-monitoring.md +1618 -0
- package/expertise/security/infrastructure/network-security.md +1337 -0
- package/expertise/security/mobile/index.md +23 -0
- package/expertise/security/mobile/mobile-android-security.md +1218 -0
- package/expertise/security/mobile/mobile-binary-protection.md +1229 -0
- package/expertise/security/mobile/mobile-data-storage.md +1265 -0
- package/expertise/security/mobile/mobile-ios-security.md +1401 -0
- package/expertise/security/mobile/mobile-network-security.md +1520 -0
- package/expertise/security/smart-contract-security.md +594 -0
- package/expertise/security/testing/index.md +22 -0
- package/expertise/security/testing/penetration-testing.md +1258 -0
- package/expertise/security/testing/security-code-review.md +1765 -0
- package/expertise/security/testing/threat-modeling.md +1074 -0
- package/expertise/security/testing/vulnerability-scanning.md +1062 -0
- package/expertise/security/web/api-security.md +586 -0
- package/expertise/security/web/cors-and-headers.md +433 -0
- package/expertise/security/web/csrf.md +562 -0
- package/expertise/security/web/file-upload.md +1477 -0
- package/expertise/security/web/index.md +25 -0
- package/expertise/security/web/injection.md +1375 -0
- package/expertise/security/web/session-management.md +1101 -0
- package/expertise/security/web/xss.md +1158 -0
- package/exports/README.md +17 -0
- package/exports/hosts/claude/.claude/agents/clarifier.md +42 -0
- package/exports/hosts/claude/.claude/agents/content-author.md +63 -0
- package/exports/hosts/claude/.claude/agents/designer.md +55 -0
- package/exports/hosts/claude/.claude/agents/executor.md +55 -0
- package/exports/hosts/claude/.claude/agents/learner.md +51 -0
- package/exports/hosts/claude/.claude/agents/planner.md +53 -0
- package/exports/hosts/claude/.claude/agents/researcher.md +43 -0
- package/exports/hosts/claude/.claude/agents/reviewer.md +54 -0
- package/exports/hosts/claude/.claude/agents/specifier.md +47 -0
- package/exports/hosts/claude/.claude/agents/verifier.md +71 -0
- package/exports/hosts/claude/.claude/commands/author.md +42 -0
- package/exports/hosts/claude/.claude/commands/clarify.md +38 -0
- package/exports/hosts/claude/.claude/commands/design-review.md +46 -0
- package/exports/hosts/claude/.claude/commands/design.md +44 -0
- package/exports/hosts/claude/.claude/commands/discover.md +37 -0
- package/exports/hosts/claude/.claude/commands/execute.md +48 -0
- package/exports/hosts/claude/.claude/commands/learn.md +38 -0
- package/exports/hosts/claude/.claude/commands/plan-review.md +42 -0
- package/exports/hosts/claude/.claude/commands/plan.md +39 -0
- package/exports/hosts/claude/.claude/commands/prepare-next.md +37 -0
- package/exports/hosts/claude/.claude/commands/review.md +40 -0
- package/exports/hosts/claude/.claude/commands/run-audit.md +41 -0
- package/exports/hosts/claude/.claude/commands/spec-challenge.md +41 -0
- package/exports/hosts/claude/.claude/commands/specify.md +38 -0
- package/exports/hosts/claude/.claude/commands/verify.md +37 -0
- package/exports/hosts/claude/.claude/settings.json +34 -0
- package/exports/hosts/claude/CLAUDE.md +19 -0
- package/exports/hosts/claude/export.manifest.json +38 -0
- package/exports/hosts/claude/host-package.json +67 -0
- package/exports/hosts/codex/AGENTS.md +19 -0
- package/exports/hosts/codex/export.manifest.json +38 -0
- package/exports/hosts/codex/host-package.json +41 -0
- package/exports/hosts/cursor/.cursor/hooks.json +16 -0
- package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +19 -0
- package/exports/hosts/cursor/export.manifest.json +38 -0
- package/exports/hosts/cursor/host-package.json +42 -0
- package/exports/hosts/gemini/GEMINI.md +19 -0
- package/exports/hosts/gemini/export.manifest.json +38 -0
- package/exports/hosts/gemini/host-package.json +41 -0
- package/hooks/README.md +18 -0
- package/hooks/definitions/loop_cap_guard.yaml +21 -0
- package/hooks/definitions/post_tool_capture.yaml +24 -0
- package/hooks/definitions/pre_compact_summary.yaml +19 -0
- package/hooks/definitions/pre_tool_capture_route.yaml +19 -0
- package/hooks/definitions/protected_path_write_guard.yaml +19 -0
- package/hooks/definitions/session_start.yaml +19 -0
- package/hooks/definitions/stop_handoff_harvest.yaml +20 -0
- package/hooks/loop-cap-guard +17 -0
- package/hooks/post-tool-lint +36 -0
- package/hooks/protected-path-write-guard +17 -0
- package/hooks/session-start +41 -0
- package/llms-full.txt +2355 -0
- package/llms.txt +43 -0
- package/package.json +79 -0
- package/roles/README.md +20 -0
- package/roles/clarifier.md +42 -0
- package/roles/content-author.md +63 -0
- package/roles/designer.md +55 -0
- package/roles/executor.md +55 -0
- package/roles/learner.md +51 -0
- package/roles/planner.md +53 -0
- package/roles/researcher.md +43 -0
- package/roles/reviewer.md +54 -0
- package/roles/specifier.md +47 -0
- package/roles/verifier.md +71 -0
- package/schemas/README.md +24 -0
- package/schemas/accepted-learning.schema.json +20 -0
- package/schemas/author-artifact.schema.json +156 -0
- package/schemas/clarification.schema.json +19 -0
- package/schemas/design-artifact.schema.json +80 -0
- package/schemas/docs-claim.schema.json +18 -0
- package/schemas/export-manifest.schema.json +20 -0
- package/schemas/hook.schema.json +67 -0
- package/schemas/host-export-package.schema.json +18 -0
- package/schemas/implementation-plan.schema.json +19 -0
- package/schemas/proposed-learning.schema.json +19 -0
- package/schemas/research.schema.json +18 -0
- package/schemas/review.schema.json +29 -0
- package/schemas/run-manifest.schema.json +18 -0
- package/schemas/spec-challenge.schema.json +18 -0
- package/schemas/spec.schema.json +20 -0
- package/schemas/usage.schema.json +102 -0
- package/schemas/verification-proof.schema.json +29 -0
- package/schemas/wazir-manifest.schema.json +173 -0
- package/skills/README.md +40 -0
- package/skills/brainstorming/SKILL.md +77 -0
- package/skills/debugging/SKILL.md +50 -0
- package/skills/design/SKILL.md +61 -0
- package/skills/dispatching-parallel-agents/SKILL.md +128 -0
- package/skills/executing-plans/SKILL.md +70 -0
- package/skills/finishing-a-development-branch/SKILL.md +169 -0
- package/skills/humanize/SKILL.md +123 -0
- package/skills/init-pipeline/SKILL.md +124 -0
- package/skills/prepare-next/SKILL.md +20 -0
- package/skills/receiving-code-review/SKILL.md +123 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +108 -0
- package/skills/run-audit/SKILL.md +197 -0
- package/skills/scan-project/SKILL.md +41 -0
- package/skills/self-audit/SKILL.md +153 -0
- package/skills/subagent-driven-development/SKILL.md +154 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
- package/skills/subagent-driven-development/implementer-prompt.md +102 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/skills/tdd/SKILL.md +23 -0
- package/skills/using-git-worktrees/SKILL.md +163 -0
- package/skills/using-skills/SKILL.md +95 -0
- package/skills/verification/SKILL.md +22 -0
- package/skills/wazir/SKILL.md +463 -0
- package/skills/writing-plans/SKILL.md +30 -0
- package/skills/writing-skills/SKILL.md +157 -0
- package/skills/writing-skills/anthropic-best-practices.md +122 -0
- package/skills/writing-skills/persuasion-principles.md +50 -0
- package/templates/README.md +20 -0
- package/templates/artifacts/README.md +10 -0
- package/templates/artifacts/accepted-learning.md +19 -0
- package/templates/artifacts/accepted-learning.template.json +12 -0
- package/templates/artifacts/author.md +74 -0
- package/templates/artifacts/author.template.json +19 -0
- package/templates/artifacts/clarification.md +21 -0
- package/templates/artifacts/clarification.template.json +12 -0
- package/templates/artifacts/execute-notes.md +19 -0
- package/templates/artifacts/implementation-plan.md +21 -0
- package/templates/artifacts/implementation-plan.template.json +11 -0
- package/templates/artifacts/learning-proposal.md +19 -0
- package/templates/artifacts/next-run-handoff.md +21 -0
- package/templates/artifacts/plan-review.md +19 -0
- package/templates/artifacts/proposed-learning.template.json +12 -0
- package/templates/artifacts/research.md +21 -0
- package/templates/artifacts/research.template.json +12 -0
- package/templates/artifacts/review-findings.md +19 -0
- package/templates/artifacts/review.template.json +11 -0
- package/templates/artifacts/run-manifest.template.json +8 -0
- package/templates/artifacts/spec-challenge.md +19 -0
- package/templates/artifacts/spec-challenge.template.json +11 -0
- package/templates/artifacts/spec.md +21 -0
- package/templates/artifacts/spec.template.json +12 -0
- package/templates/artifacts/verification-proof.md +19 -0
- package/templates/artifacts/verification-proof.template.json +11 -0
- package/templates/examples/accepted-learning.example.json +14 -0
- package/templates/examples/author.example.json +152 -0
- package/templates/examples/clarification.example.json +15 -0
- package/templates/examples/docs-claim.example.json +8 -0
- package/templates/examples/export-manifest.example.json +7 -0
- package/templates/examples/host-export-package.example.json +11 -0
- package/templates/examples/implementation-plan.example.json +17 -0
- package/templates/examples/proposed-learning.example.json +13 -0
- package/templates/examples/research.example.json +15 -0
- package/templates/examples/research.example.md +6 -0
- package/templates/examples/review.example.json +17 -0
- package/templates/examples/run-manifest.example.json +9 -0
- package/templates/examples/spec-challenge.example.json +14 -0
- package/templates/examples/spec.example.json +21 -0
- package/templates/examples/verification-proof.example.json +21 -0
- package/templates/examples/wazir-manifest.example.yaml +65 -0
- package/templates/task-definition-schema.md +99 -0
- package/tooling/README.md +20 -0
- package/tooling/src/adapters/context-mode.js +50 -0
- package/tooling/src/capture/command.js +376 -0
- package/tooling/src/capture/store.js +99 -0
- package/tooling/src/capture/usage.js +270 -0
- package/tooling/src/checks/branches.js +50 -0
- package/tooling/src/checks/brand-truth.js +110 -0
- package/tooling/src/checks/changelog.js +231 -0
- package/tooling/src/checks/command-registry.js +36 -0
- package/tooling/src/checks/commits.js +102 -0
- package/tooling/src/checks/docs-drift.js +103 -0
- package/tooling/src/checks/docs-truth.js +201 -0
- package/tooling/src/checks/runtime-surface.js +156 -0
- package/tooling/src/cli.js +116 -0
- package/tooling/src/command-options.js +56 -0
- package/tooling/src/commands/validate.js +320 -0
- package/tooling/src/doctor/command.js +91 -0
- package/tooling/src/export/command.js +77 -0
- package/tooling/src/export/compiler.js +498 -0
- package/tooling/src/guards/loop-cap-guard.js +52 -0
- package/tooling/src/guards/protected-path-write-guard.js +67 -0
- package/tooling/src/index/command.js +152 -0
- package/tooling/src/index/storage.js +1061 -0
- package/tooling/src/index/summarizers.js +261 -0
- package/tooling/src/loaders.js +18 -0
- package/tooling/src/project-root.js +22 -0
- package/tooling/src/recall/command.js +225 -0
- package/tooling/src/schema-validator.js +30 -0
- package/tooling/src/state-root.js +40 -0
- package/tooling/src/status/command.js +71 -0
- package/wazir.manifest.yaml +135 -0
- package/workflows/README.md +19 -0
- package/workflows/author.md +42 -0
- package/workflows/clarify.md +38 -0
- package/workflows/design-review.md +46 -0
- package/workflows/design.md +44 -0
- package/workflows/discover.md +37 -0
- package/workflows/execute.md +48 -0
- package/workflows/learn.md +38 -0
- package/workflows/plan-review.md +42 -0
- package/workflows/plan.md +39 -0
- package/workflows/prepare-next.md +37 -0
- package/workflows/review.md +40 -0
- package/workflows/run-audit.md +41 -0
- package/workflows/spec-challenge.md +41 -0
- package/workflows/specify.md +38 -0
- package/workflows/verify.md +37 -0
|
@@ -0,0 +1,1128 @@
|
|
|
1
|
+
# React Anti-Patterns
|
|
2
|
+
> **Domain:** Frontend
|
|
3
|
+
> **Anti-patterns covered:** 20
|
|
4
|
+
> **Highest severity:** High
|
|
5
|
+
|
|
6
|
+
React's component model and hooks API are expressive but present a wide surface area for misuse. Many of these patterns feel correct in isolation — they compile, they run, they even pass shallow tests — yet they silently degrade performance, correctness, or maintainability. This module catalogs the 20 most consequential React anti-patterns observed in production codebases, sourced from real-world audits, community post-mortems, and the official React documentation's own "you might not need an effect" guidance.
|
|
7
|
+
|
|
8
|
+
Each entry is classified by how often it appears, how badly it hurts, and how hard it is to catch without tooling.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Anti-Pattern Entries
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
### AP-01: Prop Drilling Through Many Layers
|
|
17
|
+
|
|
18
|
+
**Also known as:** Threading props, Christmas-tree props, prop tunneling
|
|
19
|
+
**Frequency:** Very Common
|
|
20
|
+
**Severity:** Medium
|
|
21
|
+
**Detection difficulty:** Easy
|
|
22
|
+
|
|
23
|
+
**What it looks like:**
|
|
24
|
+
```jsx
|
|
25
|
+
// App passes `user` through 4 layers that don't use it
|
|
26
|
+
function App() {
|
|
27
|
+
const user = useCurrentUser();
|
|
28
|
+
return <Layout user={user} />;
|
|
29
|
+
}
|
|
30
|
+
function Layout({ user }) {
|
|
31
|
+
return <Sidebar user={user} />;
|
|
32
|
+
}
|
|
33
|
+
function Sidebar({ user }) {
|
|
34
|
+
return <NavMenu user={user} />;
|
|
35
|
+
}
|
|
36
|
+
function NavMenu({ user }) {
|
|
37
|
+
return <Avatar name={user.name} />; // Only this component needs it
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Why developers do it:**
|
|
42
|
+
It works, and when a codebase starts small it is the most obvious path. Developers reach for it by default because it requires no new abstractions.
|
|
43
|
+
|
|
44
|
+
**What goes wrong:**
|
|
45
|
+
- Every intermediate component takes on a prop it does not use, coupling unrelated layers
|
|
46
|
+
- Refactoring the prop type (e.g., renaming `user.name` to `user.displayName`) requires touching every intermediate component
|
|
47
|
+
- Adding a second consumer at a different level requires reopening multiple files
|
|
48
|
+
- Tests for intermediate components must supply the prop even though it is irrelevant to their behaviour
|
|
49
|
+
|
|
50
|
+
**The fix:**
|
|
51
|
+
```jsx
|
|
52
|
+
// Before: 4-layer thread
|
|
53
|
+
// After: Context at the appropriate boundary
|
|
54
|
+
|
|
55
|
+
const UserContext = React.createContext(null);
|
|
56
|
+
|
|
57
|
+
function App() {
|
|
58
|
+
const user = useCurrentUser();
|
|
59
|
+
return (
|
|
60
|
+
<UserContext.Provider value={user}>
|
|
61
|
+
<Layout />
|
|
62
|
+
</UserContext.Provider>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function Avatar() {
|
|
67
|
+
const user = useContext(UserContext);
|
|
68
|
+
return <img src={user.avatarUrl} alt={user.name} />;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
Alternatively, use component composition (slot pattern) to pass `<Avatar />` directly without threading props.
|
|
72
|
+
|
|
73
|
+
**Detection rule:**
|
|
74
|
+
Flag any prop that appears in a component's props signature but is never read by that component's JSX or logic — only forwarded to a child.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### AP-02: Using useEffect for Derived State
|
|
79
|
+
|
|
80
|
+
**Also known as:** Effect-driven derivation, useState + useEffect sync pattern
|
|
81
|
+
**Frequency:** Very Common
|
|
82
|
+
**Severity:** High
|
|
83
|
+
**Detection difficulty:** Moderate
|
|
84
|
+
|
|
85
|
+
**What it looks like:**
|
|
86
|
+
```jsx
|
|
87
|
+
function ProductList({ products, filter }) {
|
|
88
|
+
const [filtered, setFiltered] = useState([]);
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
setFiltered(products.filter(p => p.category === filter));
|
|
92
|
+
}, [products, filter]);
|
|
93
|
+
|
|
94
|
+
return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Why developers do it:**
|
|
99
|
+
Developers treat `useEffect` as "run code when things change", which sounds like the right tool for deriving a new value from props.
|
|
100
|
+
|
|
101
|
+
**What goes wrong:**
|
|
102
|
+
- Causes a guaranteed double render: first render uses stale `filtered`, effect fires, second render uses fresh value
|
|
103
|
+
- Introduces a state variable that exists solely to hold a value calculable inline
|
|
104
|
+
- Can trigger cascading effect chains when the derived state is itself a dependency elsewhere
|
|
105
|
+
- React's documentation explicitly names this as one of the most common misuses of `useEffect`
|
|
106
|
+
|
|
107
|
+
**The fix:**
|
|
108
|
+
```jsx
|
|
109
|
+
// Before: effect + state
|
|
110
|
+
const [filtered, setFiltered] = useState([]);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
setFiltered(products.filter(p => p.category === filter));
|
|
113
|
+
}, [products, filter]);
|
|
114
|
+
|
|
115
|
+
// After: plain derivation during render
|
|
116
|
+
const filtered = products.filter(p => p.category === filter);
|
|
117
|
+
// Wrap in useMemo only if the computation is measurably expensive
|
|
118
|
+
const filtered = useMemo(
|
|
119
|
+
() => products.filter(p => p.category === filter),
|
|
120
|
+
[products, filter]
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Detection rule:**
|
|
125
|
+
Flag any `useEffect` whose sole body is a `setState` call where the new value is a pure transformation of the effect's dependencies.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### AP-03: Index as List Key
|
|
130
|
+
|
|
131
|
+
**Also known as:** Array index key, positional key
|
|
132
|
+
**Frequency:** Very Common
|
|
133
|
+
**Severity:** High
|
|
134
|
+
**Detection difficulty:** Easy
|
|
135
|
+
|
|
136
|
+
**What it looks like:**
|
|
137
|
+
```jsx
|
|
138
|
+
function TodoList({ todos }) {
|
|
139
|
+
return (
|
|
140
|
+
<ul>
|
|
141
|
+
{todos.map((todo, index) => (
|
|
142
|
+
<TodoItem key={index} todo={todo} /> // index as key
|
|
143
|
+
))}
|
|
144
|
+
</ul>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Why developers do it:**
|
|
150
|
+
`index` is always available when mapping, so it is the path of least resistance. ESLint's `react/jsx-key` rule only checks that a key exists — not that it is stable.
|
|
151
|
+
|
|
152
|
+
**What goes wrong:**
|
|
153
|
+
- When items are reordered, added to the middle, or removed, React reconciles by position rather than identity, causing wrong components to be updated, wrong animations to fire, and uncontrolled inputs to show stale values
|
|
154
|
+
- Component state (form inputs, scroll position, focus) migrates to the wrong item silently
|
|
155
|
+
- Particularly destructive in drag-and-drop lists and paginated tables
|
|
156
|
+
|
|
157
|
+
**The fix:**
|
|
158
|
+
```jsx
|
|
159
|
+
// Before: index key
|
|
160
|
+
todos.map((todo, index) => <TodoItem key={index} todo={todo} />)
|
|
161
|
+
|
|
162
|
+
// After: stable unique key
|
|
163
|
+
todos.map(todo => <TodoItem key={todo.id} todo={todo} />)
|
|
164
|
+
|
|
165
|
+
// If items have no ID, derive a stable key from a unique field combination
|
|
166
|
+
todos.map(todo => <TodoItem key={`${todo.userId}-${todo.createdAt}`} todo={todo} />)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Detection rule:**
|
|
170
|
+
Flag any `key={index}` or `key={i}` where the variable name matches the second argument of a `.map()` callback.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### AP-04: Mutating State Directly
|
|
175
|
+
|
|
176
|
+
**Also known as:** Direct state mutation, in-place array/object mutation
|
|
177
|
+
**Frequency:** Very Common
|
|
178
|
+
**Severity:** High
|
|
179
|
+
**Detection difficulty:** Moderate
|
|
180
|
+
|
|
181
|
+
**What it looks like:**
|
|
182
|
+
```jsx
|
|
183
|
+
function Cart() {
|
|
184
|
+
const [items, setItems] = useState([]);
|
|
185
|
+
|
|
186
|
+
function addItem(item) {
|
|
187
|
+
items.push(item); // mutation
|
|
188
|
+
setItems(items); // same reference — React bails out
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function updateQty(id, qty) {
|
|
192
|
+
const item = items.find(i => i.id === id);
|
|
193
|
+
item.qty = qty; // nested mutation
|
|
194
|
+
setItems([...items]); // spread creates new array but objects are still mutated
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Why developers do it:**
|
|
200
|
+
JavaScript arrays and objects are mutable by default. Developers coming from non-React backgrounds (or from class components with `this.state`) expect mutation to be the normal model.
|
|
201
|
+
|
|
202
|
+
**What goes wrong:**
|
|
203
|
+
- `setItems(items)` after mutation passes the same reference; React's `Object.is` check short-circuits and skips the re-render
|
|
204
|
+
- Mutated objects break `React.memo`, `useMemo`, and `useCallback` because their dependencies appear unchanged
|
|
205
|
+
- Mutations bypass Redux DevTools time-travel and React DevTools state snapshots
|
|
206
|
+
- Concurrently rendered trees can read inconsistent intermediate states
|
|
207
|
+
|
|
208
|
+
**The fix:**
|
|
209
|
+
```jsx
|
|
210
|
+
// Before: mutation
|
|
211
|
+
items.push(item);
|
|
212
|
+
setItems(items);
|
|
213
|
+
|
|
214
|
+
// After: new reference
|
|
215
|
+
setItems(prev => [...prev, item]);
|
|
216
|
+
|
|
217
|
+
// Before: nested mutation
|
|
218
|
+
item.qty = qty;
|
|
219
|
+
setItems([...items]);
|
|
220
|
+
|
|
221
|
+
// After: immutable update
|
|
222
|
+
setItems(prev =>
|
|
223
|
+
prev.map(i => i.id === id ? { ...i, qty } : i)
|
|
224
|
+
);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Detection rule:**
|
|
228
|
+
Flag calls to `.push()`, `.pop()`, `.splice()`, `.sort()`, `.reverse()`, or direct property assignment (`obj.key = value`) on variables that are the first argument of a `useState` destructure or `useReducer` state binding.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
### AP-05: Creating Components Inside Render
|
|
233
|
+
|
|
234
|
+
**Also known as:** Inline component definition, component factory inside JSX, nested component declaration
|
|
235
|
+
**Frequency:** Common
|
|
236
|
+
**Severity:** High
|
|
237
|
+
**Detection difficulty:** Easy
|
|
238
|
+
|
|
239
|
+
**What it looks like:**
|
|
240
|
+
```jsx
|
|
241
|
+
function ParentList({ items }) {
|
|
242
|
+
// New function reference every render
|
|
243
|
+
const Item = ({ text }) => <li className="item">{text}</li>;
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<ul>
|
|
247
|
+
{items.map(item => <Item key={item.id} text={item.label} />)}
|
|
248
|
+
</ul>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Why developers do it:**
|
|
254
|
+
It keeps related code co-located and avoids creating a separate named component for something that feels "small". It also gives the inner component easy access to the parent's scope.
|
|
255
|
+
|
|
256
|
+
**What goes wrong:**
|
|
257
|
+
- Every render of `ParentList` creates a new `Item` function reference
|
|
258
|
+
- React sees a new component type at the same tree position and unmounts + remounts every `<Item />` — destroying DOM nodes, focus state, and any component-local state
|
|
259
|
+
- Negates any memoisation on the inner component entirely
|
|
260
|
+
- Creates invisible performance cliffs that only appear under load
|
|
261
|
+
|
|
262
|
+
**The fix:**
|
|
263
|
+
```jsx
|
|
264
|
+
// Before: defined inside render
|
|
265
|
+
function ParentList({ items }) {
|
|
266
|
+
const Item = ({ text }) => <li>{text}</li>;
|
|
267
|
+
return <ul>{items.map(item => <Item key={item.id} text={item.label} />)}</ul>;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// After: defined at module scope (or in its own file)
|
|
271
|
+
function Item({ text }) {
|
|
272
|
+
return <li className="item">{text}</li>;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function ParentList({ items }) {
|
|
276
|
+
return <ul>{items.map(item => <Item key={item.id} text={item.label} />)}</ul>;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Detection rule:**
|
|
281
|
+
Flag any function or arrow-function declaration that (a) returns JSX, (b) begins with an uppercase letter, and (c) is declared inside the body of another React component function.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
### AP-06: useEffect as onChange Handler
|
|
286
|
+
|
|
287
|
+
**Also known as:** Reactive effect for synchronous events, watching state with effects
|
|
288
|
+
**Frequency:** Very Common
|
|
289
|
+
**Severity:** Medium
|
|
290
|
+
**Detection difficulty:** Moderate
|
|
291
|
+
|
|
292
|
+
**What it looks like:**
|
|
293
|
+
```jsx
|
|
294
|
+
function SearchBox() {
|
|
295
|
+
const [query, setQuery] = useState('');
|
|
296
|
+
const [results, setResults] = useState([]);
|
|
297
|
+
|
|
298
|
+
// Treating useEffect like an onChange watcher
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
if (query) {
|
|
301
|
+
const filtered = allItems.filter(i => i.name.includes(query));
|
|
302
|
+
setResults(filtered);
|
|
303
|
+
}
|
|
304
|
+
}, [query]);
|
|
305
|
+
|
|
306
|
+
return <input value={query} onChange={e => setQuery(e.target.value)} />;
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Why developers do it:**
|
|
311
|
+
The mental model of "do X whenever Y changes" maps naturally to `useEffect`. It mimics Vue's watchers and MobX reactions.
|
|
312
|
+
|
|
313
|
+
**What goes wrong:**
|
|
314
|
+
- The state update in the effect schedules a second render; synchronous derived logic can run inline
|
|
315
|
+
- Creates temporal coupling: the user sees the old `results` for one render cycle before the effect fires
|
|
316
|
+
- Harder to trace the data flow — `results` appears to update magically rather than at the call site
|
|
317
|
+
- Opens the door for accidental infinite loops when the derived state is also a dependency
|
|
318
|
+
|
|
319
|
+
**The fix:**
|
|
320
|
+
```jsx
|
|
321
|
+
// Before: effect watcher
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
setResults(allItems.filter(i => i.name.includes(query)));
|
|
324
|
+
}, [query]);
|
|
325
|
+
|
|
326
|
+
// After: inline derivation (synchronous, zero extra renders)
|
|
327
|
+
const results = query
|
|
328
|
+
? allItems.filter(i => i.name.includes(query))
|
|
329
|
+
: [];
|
|
330
|
+
// Move setQuery+derivation into the event handler if side effects are also needed
|
|
331
|
+
function handleChange(e) {
|
|
332
|
+
const q = e.target.value;
|
|
333
|
+
setQuery(q);
|
|
334
|
+
// fire async fetch here if needed, not in an effect watching query
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Detection rule:**
|
|
339
|
+
Flag any `useEffect` that watches a single state variable and whose only body is a `setState` call producing a derived value from that variable.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
### AP-07: Not Cleaning Up Effects
|
|
344
|
+
|
|
345
|
+
**Also known as:** Leaking effects, missing cleanup, unmounted component state updates
|
|
346
|
+
**Frequency:** Common
|
|
347
|
+
**Severity:** High
|
|
348
|
+
**Detection difficulty:** Moderate
|
|
349
|
+
|
|
350
|
+
**What it looks like:**
|
|
351
|
+
```jsx
|
|
352
|
+
function UserProfile({ userId }) {
|
|
353
|
+
const [user, setUser] = useState(null);
|
|
354
|
+
|
|
355
|
+
useEffect(() => {
|
|
356
|
+
fetch(`/api/users/${userId}`)
|
|
357
|
+
.then(r => r.json())
|
|
358
|
+
.then(data => setUser(data)); // setState after potential unmount
|
|
359
|
+
}, [userId]);
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Why developers do it:**
|
|
364
|
+
The happy path works. Most developers learn the two-argument form of `useEffect` but are not taught that the callback's return value is a cleanup function.
|
|
365
|
+
|
|
366
|
+
**What goes wrong:**
|
|
367
|
+
- If the component unmounts before the fetch resolves, `setUser` runs on an unmounted component, producing a React warning and, in some cases, subtle state corruption if the component remounts
|
|
368
|
+
- Race conditions: rapid `userId` changes queue multiple fetches; a slow earlier response can overwrite a fast later one
|
|
369
|
+
- Subscriptions (WebSockets, event listeners, timers) run indefinitely after the component is gone, leaking memory and producing phantom state updates
|
|
370
|
+
|
|
371
|
+
**The fix:**
|
|
372
|
+
```jsx
|
|
373
|
+
useEffect(() => {
|
|
374
|
+
let cancelled = false;
|
|
375
|
+
const controller = new AbortController();
|
|
376
|
+
|
|
377
|
+
fetch(`/api/users/${userId}`, { signal: controller.signal })
|
|
378
|
+
.then(r => r.json())
|
|
379
|
+
.then(data => {
|
|
380
|
+
if (!cancelled) setUser(data);
|
|
381
|
+
})
|
|
382
|
+
.catch(err => {
|
|
383
|
+
if (err.name !== 'AbortError') setError(err);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
return () => {
|
|
387
|
+
cancelled = true;
|
|
388
|
+
controller.abort();
|
|
389
|
+
};
|
|
390
|
+
}, [userId]);
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Detection rule:**
|
|
394
|
+
Flag any `useEffect` that contains a `fetch()` call, a `setTimeout`/`setInterval`, or an `.addEventListener()` call and whose callback does not return a cleanup function.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
### AP-08: Unnecessary useMemo / useCallback
|
|
399
|
+
|
|
400
|
+
**Also known as:** Premature memoisation, defensive wrapping, cargo-cult hooks
|
|
401
|
+
**Frequency:** Very Common
|
|
402
|
+
**Severity:** Low
|
|
403
|
+
**Detection difficulty:** Hard
|
|
404
|
+
|
|
405
|
+
**What it looks like:**
|
|
406
|
+
```jsx
|
|
407
|
+
function Greeting({ name }) {
|
|
408
|
+
// Memoising a trivial string concatenation
|
|
409
|
+
const greeting = useMemo(() => `Hello, ${name}!`, [name]);
|
|
410
|
+
|
|
411
|
+
// Memoising a handler on a non-memoised child
|
|
412
|
+
const handleClick = useCallback(() => {
|
|
413
|
+
console.log('clicked');
|
|
414
|
+
}, []);
|
|
415
|
+
|
|
416
|
+
return <button onClick={handleClick}>{greeting}</button>;
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**Why developers do it:**
|
|
421
|
+
Developers cargo-cult patterns from performance-critical code, "just to be safe". Code reviews sometimes reward defensive memoisation without verifying that the memoised boundary is actually a performance bottleneck.
|
|
422
|
+
|
|
423
|
+
**What goes wrong:**
|
|
424
|
+
- `useMemo` and `useCallback` add overhead on every render (dependency comparison, closure allocation) — they make the initial render measurably slower
|
|
425
|
+
- The memoised value is only useful if the consumer is wrapped in `React.memo`; without that, the parent re-render forces child re-renders regardless
|
|
426
|
+
- Adds cognitive noise, making it harder to distinguish meaningful memoisation from boilerplate
|
|
427
|
+
- With the React Compiler (stable as of late 2025), manual memoisation is largely redundant and can conflict with compiler optimisations
|
|
428
|
+
|
|
429
|
+
**The fix:**
|
|
430
|
+
Remove the hooks. Add them back only after profiling shows a specific render as a bottleneck, and only when the memoised value is consumed by a `React.memo`-wrapped child or is a referentially stable dependency of another hook.
|
|
431
|
+
|
|
432
|
+
**Detection rule:**
|
|
433
|
+
Flag `useMemo` calls whose computation is a simple expression (string template, arithmetic, property access) or `useCallback` calls whose dependencies array is `[]` and the callback is not passed to a `React.memo`-wrapped component.
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
### AP-09: Missing useMemo / useCallback Causing Re-renders
|
|
438
|
+
|
|
439
|
+
**Also known as:** New reference on every render, unstable prop identity, memoisation gap
|
|
440
|
+
**Frequency:** Common
|
|
441
|
+
**Severity:** High
|
|
442
|
+
**Detection difficulty:** Hard
|
|
443
|
+
|
|
444
|
+
**What it looks like:**
|
|
445
|
+
```jsx
|
|
446
|
+
function Dashboard({ userId }) {
|
|
447
|
+
// New object reference every render
|
|
448
|
+
const config = { userId, theme: 'dark' };
|
|
449
|
+
|
|
450
|
+
// New function reference every render
|
|
451
|
+
const fetchData = () => fetch(`/api/data?user=${userId}`);
|
|
452
|
+
|
|
453
|
+
return <ExpensiveChart config={config} onRefresh={fetchData} />;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ExpensiveChart is wrapped in React.memo — but memo is useless here
|
|
457
|
+
const ExpensiveChart = React.memo(({ config, onRefresh }) => { ... });
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**Why developers do it:**
|
|
461
|
+
The broken memoisation chain is invisible. `React.memo` looks correct in isolation, but the parent silently destroys its effectiveness by producing new references every render.
|
|
462
|
+
|
|
463
|
+
**What goes wrong:**
|
|
464
|
+
- `React.memo` performs a shallow comparison; new object/function references always fail the comparison, triggering a re-render regardless of whether the data changed
|
|
465
|
+
- Expensive computations re-run on every parent re-render
|
|
466
|
+
- Can cause cascading re-renders through a subtree
|
|
467
|
+
|
|
468
|
+
**The fix:**
|
|
469
|
+
```jsx
|
|
470
|
+
function Dashboard({ userId }) {
|
|
471
|
+
const config = useMemo(() => ({ userId, theme: 'dark' }), [userId]);
|
|
472
|
+
const fetchData = useCallback(() => fetch(`/api/data?user=${userId}`), [userId]);
|
|
473
|
+
|
|
474
|
+
return <ExpensiveChart config={config} onRefresh={fetchData} />;
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Detection rule:**
|
|
479
|
+
Flag object literals (`{}`) and function expressions/arrow functions created inline as JSX props on components that are wrapped in `React.memo`.
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
### AP-10: God Component (500+ Line Components)
|
|
484
|
+
|
|
485
|
+
**Also known as:** Monolithic component, kitchen-sink component, do-everything component
|
|
486
|
+
**Frequency:** Common
|
|
487
|
+
**Severity:** High
|
|
488
|
+
**Detection difficulty:** Easy
|
|
489
|
+
|
|
490
|
+
**What it looks like:**
|
|
491
|
+
```jsx
|
|
492
|
+
// UserDashboard.jsx — 800 lines
|
|
493
|
+
function UserDashboard() {
|
|
494
|
+
// 15+ useState hooks
|
|
495
|
+
// 8+ useEffect hooks
|
|
496
|
+
// Inline form handling, API fetching, data transformation,
|
|
497
|
+
// role-based rendering, modal management, CSV export logic...
|
|
498
|
+
return (
|
|
499
|
+
<div>
|
|
500
|
+
{/* 300 lines of deeply nested JSX */}
|
|
501
|
+
</div>
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
**Why developers do it:**
|
|
507
|
+
Features accrete incrementally. A 100-line component becomes 200 lines, then 400, each addition feeling like a small change. The cost is invisible until the component becomes unmaintainable.
|
|
508
|
+
|
|
509
|
+
**What goes wrong:**
|
|
510
|
+
- A single change requires reading and reasoning about hundreds of lines of unrelated logic
|
|
511
|
+
- Testing requires mocking every concern the component touches
|
|
512
|
+
- Re-renders are expensive because a single state change invalidates a massive render tree
|
|
513
|
+
- Impossible to reuse sub-behaviours in other contexts
|
|
514
|
+
- New developers on the team avoid touching the component, causing further feature drift
|
|
515
|
+
|
|
516
|
+
**The fix:**
|
|
517
|
+
Extract by concern:
|
|
518
|
+
- Split data-fetching into a custom hook (`useUserDashboardData`)
|
|
519
|
+
- Extract each logical section into its own component (`UserProfile`, `ActivityFeed`, `ExportPanel`)
|
|
520
|
+
- Move business logic out of JSX into pure functions
|
|
521
|
+
|
|
522
|
+
Target: no component should exceed ~150 lines; no component should contain more than 3–4 `useEffect` hooks.
|
|
523
|
+
|
|
524
|
+
**Detection rule:**
|
|
525
|
+
Flag any component function whose source length exceeds 200 lines or contains more than 5 `useState` calls.
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
### AP-11: Using Context for Frequently Changing Values
|
|
530
|
+
|
|
531
|
+
**Also known as:** God context, context performance trap, high-frequency context updates
|
|
532
|
+
**Frequency:** Common
|
|
533
|
+
**Severity:** High
|
|
534
|
+
**Detection difficulty:** Hard
|
|
535
|
+
|
|
536
|
+
**What it looks like:**
|
|
537
|
+
```jsx
|
|
538
|
+
const AppContext = React.createContext();
|
|
539
|
+
|
|
540
|
+
function App() {
|
|
541
|
+
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
|
|
542
|
+
const [user, setUser] = useState(null);
|
|
543
|
+
const [theme, setTheme] = useState('light');
|
|
544
|
+
|
|
545
|
+
// Mouse position changes 60 times/second — all consumers re-render
|
|
546
|
+
return (
|
|
547
|
+
<AppContext.Provider value={{ mousePos, user, theme, setTheme }}>
|
|
548
|
+
<Router />
|
|
549
|
+
</AppContext.Provider>
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
**Why developers do it:**
|
|
555
|
+
Context is introduced to solve prop drilling, then values are added to it progressively. It starts with `user` and `theme`, then gains `mousePos`, `notifications`, cart counts, and more.
|
|
556
|
+
|
|
557
|
+
**What goes wrong:**
|
|
558
|
+
- Every consumer of the context re-renders whenever **any** value in the context object changes
|
|
559
|
+
- Frequently updating values (mouse position, animation frame counters, live websocket data) will cause every consumer to re-render at that frequency
|
|
560
|
+
- React has no built-in mechanism to subscribe to a subset of context values
|
|
561
|
+
|
|
562
|
+
**The fix:**
|
|
563
|
+
```jsx
|
|
564
|
+
// Split contexts by change frequency
|
|
565
|
+
const UserContext = React.createContext(null); // static after login
|
|
566
|
+
const ThemeContext = React.createContext('light'); // rare changes
|
|
567
|
+
const MouseContext = React.createContext({ x: 0, y: 0 }); // frequent — move to Zustand/Jotai or pass directly
|
|
568
|
+
|
|
569
|
+
function App() {
|
|
570
|
+
return (
|
|
571
|
+
<UserContext.Provider value={user}>
|
|
572
|
+
<ThemeContext.Provider value={theme}>
|
|
573
|
+
<Router />
|
|
574
|
+
</ThemeContext.Provider>
|
|
575
|
+
</UserContext.Provider>
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
For high-frequency values, prefer a fine-grained state library (Zustand, Jotai) or a subscription-based pattern.
|
|
580
|
+
|
|
581
|
+
**Detection rule:**
|
|
582
|
+
Flag context `value` props that are object literals containing more than 4 keys, or that include values derived from `requestAnimationFrame`, scroll, or mouse event handlers.
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
586
|
+
### AP-12: Fetching in useEffect Without Abort / Cleanup
|
|
587
|
+
|
|
588
|
+
**Also known as:** Unguarded fetch, race-condition fetch, fire-and-forget effect
|
|
589
|
+
**Frequency:** Very Common
|
|
590
|
+
**Severity:** High
|
|
591
|
+
**Detection difficulty:** Easy
|
|
592
|
+
|
|
593
|
+
**What it looks like:**
|
|
594
|
+
```jsx
|
|
595
|
+
function ArticleView({ articleId }) {
|
|
596
|
+
const [article, setArticle] = useState(null);
|
|
597
|
+
|
|
598
|
+
useEffect(() => {
|
|
599
|
+
fetch(`/api/articles/${articleId}`)
|
|
600
|
+
.then(r => r.json())
|
|
601
|
+
.then(setArticle);
|
|
602
|
+
// No cleanup, no abort, no race-condition guard
|
|
603
|
+
}, [articleId]);
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**Why developers do it:**
|
|
608
|
+
The basic `fetch` + `useEffect` pattern is taught in introductory React courses without the cleanup addendum. The bug only manifests under fast navigation or slow networks.
|
|
609
|
+
|
|
610
|
+
**What goes wrong:**
|
|
611
|
+
- Navigating between articles quickly can cause an older, slower request to resolve after a newer one, displaying stale content
|
|
612
|
+
- React's StrictMode double-invokes effects, immediately triggering a second fetch that the first cannot cancel
|
|
613
|
+
- Memory leaks in long-running SPAs as references pile up
|
|
614
|
+
- "Can't perform a React state update on an unmounted component" warnings in production logs
|
|
615
|
+
|
|
616
|
+
**The fix:**
|
|
617
|
+
```jsx
|
|
618
|
+
useEffect(() => {
|
|
619
|
+
const controller = new AbortController();
|
|
620
|
+
setLoading(true);
|
|
621
|
+
|
|
622
|
+
fetch(`/api/articles/${articleId}`, { signal: controller.signal })
|
|
623
|
+
.then(r => r.json())
|
|
624
|
+
.then(data => {
|
|
625
|
+
setArticle(data);
|
|
626
|
+
setLoading(false);
|
|
627
|
+
})
|
|
628
|
+
.catch(err => {
|
|
629
|
+
if (err.name !== 'AbortError') setError(err.message);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
return () => controller.abort();
|
|
633
|
+
}, [articleId]);
|
|
634
|
+
```
|
|
635
|
+
Better still: use a data-fetching library (React Query, SWR) that handles all of this by default.
|
|
636
|
+
|
|
637
|
+
**Detection rule:**
|
|
638
|
+
Flag any `useEffect` containing a `fetch()` call that does not return a cleanup function calling `AbortController.abort()` or equivalent.
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
### AP-13: Not Handling Loading / Error / Empty States
|
|
643
|
+
|
|
644
|
+
**Also known as:** Happy-path-only UI, missing state branches, undefined UI
|
|
645
|
+
**Frequency:** Very Common
|
|
646
|
+
**Severity:** Medium
|
|
647
|
+
**Detection difficulty:** Easy
|
|
648
|
+
|
|
649
|
+
**What it looks like:**
|
|
650
|
+
```jsx
|
|
651
|
+
function UserList() {
|
|
652
|
+
const [users, setUsers] = useState([]);
|
|
653
|
+
|
|
654
|
+
useEffect(() => {
|
|
655
|
+
fetch('/api/users').then(r => r.json()).then(setUsers);
|
|
656
|
+
}, []);
|
|
657
|
+
|
|
658
|
+
// What shows during load? What if fetch fails? What if users is []?
|
|
659
|
+
return (
|
|
660
|
+
<ul>
|
|
661
|
+
{users.map(u => <li key={u.id}>{u.name}</li>)}
|
|
662
|
+
</ul>
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
**Why developers do it:**
|
|
668
|
+
Initial development focuses on the data-available path. Empty and error states are treated as edge cases to add "later" — and later often never arrives.
|
|
669
|
+
|
|
670
|
+
**What goes wrong:**
|
|
671
|
+
- Users see a blank screen during fetch with no indication that anything is happening
|
|
672
|
+
- A failed request is silent; users do not know whether to wait or retry
|
|
673
|
+
- An empty response looks identical to a loading state or an error state
|
|
674
|
+
- Production incidents are harder to debug because the UI gives no indication of which branch was hit
|
|
675
|
+
|
|
676
|
+
**The fix:**
|
|
677
|
+
```jsx
|
|
678
|
+
function UserList() {
|
|
679
|
+
const [users, setUsers] = useState([]);
|
|
680
|
+
const [loading, setLoading] = useState(true);
|
|
681
|
+
const [error, setError] = useState(null);
|
|
682
|
+
|
|
683
|
+
useEffect(() => { /* ... with cleanup ... */ }, []);
|
|
684
|
+
|
|
685
|
+
if (loading) return <Spinner />;
|
|
686
|
+
if (error) return <ErrorMessage message={error} onRetry={refetch} />;
|
|
687
|
+
if (users.length === 0) return <EmptyState message="No users found." />;
|
|
688
|
+
|
|
689
|
+
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
**Detection rule:**
|
|
694
|
+
Flag any component that renders a list derived from an async source but does not contain branches for at least two of: loading state, error state, empty-array state.
|
|
695
|
+
|
|
696
|
+
---
|
|
697
|
+
|
|
698
|
+
### AP-14: Using dangerouslySetInnerHTML
|
|
699
|
+
|
|
700
|
+
**Also known as:** Raw HTML injection, innerHTML prop, XSS gateway
|
|
701
|
+
**Frequency:** Occasional
|
|
702
|
+
**Severity:** High
|
|
703
|
+
**Detection difficulty:** Easy
|
|
704
|
+
|
|
705
|
+
**What it looks like:**
|
|
706
|
+
```jsx
|
|
707
|
+
function BlogPost({ post }) {
|
|
708
|
+
return (
|
|
709
|
+
<article
|
|
710
|
+
dangerouslySetInnerHTML={{ __html: post.content }}
|
|
711
|
+
/>
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
**Why developers do it:**
|
|
717
|
+
CMS content, rich-text editors, and Markdown renderers produce HTML strings. `dangerouslySetInnerHTML` is the only built-in way to inject raw HTML into the DOM, and its name is sometimes read as a warning rather than a stop sign.
|
|
718
|
+
|
|
719
|
+
**What goes wrong:**
|
|
720
|
+
- If `post.content` contains any user-controlled data (comments, user profile fields, URL parameters) an attacker can inject `<script>` tags or event handler attributes, achieving stored or reflected XSS
|
|
721
|
+
- React's automatic HTML escaping is completely bypassed
|
|
722
|
+
- Content Security Policy headers may not fully mitigate DOM-based injection
|
|
723
|
+
- Search engine crawlers may not index dynamically injected HTML reliably
|
|
724
|
+
|
|
725
|
+
**The fix:**
|
|
726
|
+
```jsx
|
|
727
|
+
import DOMPurify from 'dompurify';
|
|
728
|
+
|
|
729
|
+
// Option A: sanitise before injecting
|
|
730
|
+
function BlogPost({ post }) {
|
|
731
|
+
const clean = DOMPurify.sanitize(post.content);
|
|
732
|
+
return <article dangerouslySetInnerHTML={{ __html: clean }} />;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Option B (preferred): use a Markdown/rich-text renderer that produces React elements
|
|
736
|
+
import ReactMarkdown from 'react-markdown';
|
|
737
|
+
function BlogPost({ post }) {
|
|
738
|
+
return <article><ReactMarkdown>{post.markdownContent}</ReactMarkdown></article>;
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
**Detection rule:**
|
|
743
|
+
Flag every occurrence of `dangerouslySetInnerHTML` as requiring a security review comment; flag any occurrence where the `__html` value is not the return value of a recognised sanitisation function (`DOMPurify.sanitize`, `sanitizeHtml`, etc.).
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
### AP-15: Not Using Error Boundaries
|
|
748
|
+
|
|
749
|
+
**Also known as:** Missing error boundary, unguarded render tree, uncaught render error
|
|
750
|
+
**Frequency:** Common
|
|
751
|
+
**Severity:** High
|
|
752
|
+
**Detection difficulty:** Easy
|
|
753
|
+
|
|
754
|
+
**What it looks like:**
|
|
755
|
+
```jsx
|
|
756
|
+
// Root renders everything with no error boundary
|
|
757
|
+
function App() {
|
|
758
|
+
return (
|
|
759
|
+
<Router>
|
|
760
|
+
<UserProfile /> {/* Any render error here... */}
|
|
761
|
+
<ActivityFeed /> {/* ...or here... */}
|
|
762
|
+
<RecommendedPosts /> {/* ...or here unmounts the entire app */}
|
|
763
|
+
</Router>
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
**Why developers do it:**
|
|
769
|
+
React's Error Boundary API requires a class component, which feels out of place in a hooks-based codebase. Developers either don't know the API or delay adding it.
|
|
770
|
+
|
|
771
|
+
**What goes wrong:**
|
|
772
|
+
- An unhandled JavaScript error during render, in a lifecycle method, or in a constructor propagates up and unmounts the **entire React tree** — users see a blank white screen
|
|
773
|
+
- No user-facing recovery path; the only option is a full page reload
|
|
774
|
+
- Errors in third-party components (analytics widgets, ad scripts embedded in React trees) can bring down the whole application
|
|
775
|
+
|
|
776
|
+
**The fix:**
|
|
777
|
+
```jsx
|
|
778
|
+
// Use react-error-boundary (de-facto standard utility)
|
|
779
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
780
|
+
|
|
781
|
+
function App() {
|
|
782
|
+
return (
|
|
783
|
+
<ErrorBoundary fallback={<AppCrashPage />}>
|
|
784
|
+
<Router>
|
|
785
|
+
<ErrorBoundary fallback={<WidgetError />}>
|
|
786
|
+
<UserProfile />
|
|
787
|
+
</ErrorBoundary>
|
|
788
|
+
<ActivityFeed />
|
|
789
|
+
</Router>
|
|
790
|
+
</ErrorBoundary>
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
```
|
|
794
|
+
Add error boundaries at: (1) the app root, (2) each major route, (3) each independently-loaded widget or panel.
|
|
795
|
+
|
|
796
|
+
**Detection rule:**
|
|
797
|
+
Flag any `ReactDOM.render` or `createRoot().render()` call where the root component is not wrapped in, or does not itself contain, at least one `ErrorBoundary` (class or `react-error-boundary`).
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
### AP-16: Mixing Controlled and Uncontrolled Components
|
|
802
|
+
|
|
803
|
+
**Also known as:** Hybrid input, value/defaultValue conflict, controlled-to-uncontrolled switch
|
|
804
|
+
**Frequency:** Common
|
|
805
|
+
**Severity:** Medium
|
|
806
|
+
**Detection difficulty:** Easy
|
|
807
|
+
|
|
808
|
+
**What it looks like:**
|
|
809
|
+
```jsx
|
|
810
|
+
function ProfileForm({ initialName }) {
|
|
811
|
+
const [name, setName] = useState(initialName); // may be undefined initially
|
|
812
|
+
|
|
813
|
+
return (
|
|
814
|
+
<input
|
|
815
|
+
value={name} // controlled when name is defined
|
|
816
|
+
onChange={e => setName(e.target.value)}
|
|
817
|
+
/>
|
|
818
|
+
// If initialName is undefined, value={undefined} makes React treat this
|
|
819
|
+
// as uncontrolled; when state later becomes a string, React emits a warning
|
|
820
|
+
// and behaviour is undefined
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
**Why developers do it:**
|
|
826
|
+
The bug is triggered by an undefined or null initial value, which is easy to miss in development when data is always present but manifests in production during loading states.
|
|
827
|
+
|
|
828
|
+
**What goes wrong:**
|
|
829
|
+
- React warns: "A component is changing an uncontrolled input to be controlled"
|
|
830
|
+
- The input's internal DOM value and React's state value diverge, causing the input to display stale data
|
|
831
|
+
- Form submission may read the wrong value depending on which controller wins
|
|
832
|
+
- Hydration mismatches in SSR applications
|
|
833
|
+
|
|
834
|
+
**The fix:**
|
|
835
|
+
```jsx
|
|
836
|
+
// Always initialise state with a defined value
|
|
837
|
+
const [name, setName] = useState(initialName ?? '');
|
|
838
|
+
|
|
839
|
+
// Or choose uncontrolled deliberately with a ref
|
|
840
|
+
const nameRef = useRef(null);
|
|
841
|
+
<input ref={nameRef} defaultValue={initialName ?? ''} />
|
|
842
|
+
```
|
|
843
|
+
Never mix `value` and `defaultValue` on the same input. Never let a controlled input's `value` become `undefined` or `null`.
|
|
844
|
+
|
|
845
|
+
**Detection rule:**
|
|
846
|
+
Flag inputs that use `value={someVar}` where `someVar` could be `undefined` or `null` based on its type annotation or initial `useState` value.
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
|
|
850
|
+
### AP-17: useState for Everything (Not Lifting State)
|
|
851
|
+
|
|
852
|
+
**Also known as:** State isolation, duplicate state, out-of-sync sibling state
|
|
853
|
+
**Frequency:** Very Common
|
|
854
|
+
**Severity:** Medium
|
|
855
|
+
**Detection difficulty:** Moderate
|
|
856
|
+
|
|
857
|
+
**What it looks like:**
|
|
858
|
+
```jsx
|
|
859
|
+
function App() {
|
|
860
|
+
return (
|
|
861
|
+
<>
|
|
862
|
+
<FilterPanel /> {/* owns selectedCategory state */}
|
|
863
|
+
<ProductList /> {/* needs selectedCategory but can't access it */}
|
|
864
|
+
</>
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function FilterPanel() {
|
|
869
|
+
const [selectedCategory, setSelectedCategory] = useState('all');
|
|
870
|
+
// ProductList can never know about this
|
|
871
|
+
}
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
**Why developers do it:**
|
|
875
|
+
Co-locating state with the component that first needs it is correct practice — until a sibling component also needs it. Developers often add a second `useState` in the sibling and try to keep them in sync with effects.
|
|
876
|
+
|
|
877
|
+
**What goes wrong:**
|
|
878
|
+
- Two copies of the same truth must be kept in sync manually (usually with effects — see AP-02 and AP-06)
|
|
879
|
+
- Sync logic is fragile; any event path that updates one copy but not the other causes stale UI
|
|
880
|
+
- The data flow becomes implicit and hard to trace
|
|
881
|
+
- Tests must simulate both state machines
|
|
882
|
+
|
|
883
|
+
**The fix:**
|
|
884
|
+
```jsx
|
|
885
|
+
// Lift to the lowest common ancestor
|
|
886
|
+
function App() {
|
|
887
|
+
const [selectedCategory, setSelectedCategory] = useState('all');
|
|
888
|
+
return (
|
|
889
|
+
<>
|
|
890
|
+
<FilterPanel category={selectedCategory} onCategoryChange={setSelectedCategory} />
|
|
891
|
+
<ProductList category={selectedCategory} />
|
|
892
|
+
</>
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
**Detection rule:**
|
|
898
|
+
Flag sibling components that declare `useState` with names that appear semantically equivalent (e.g., `selectedId`/`activeId`, `filter`/`currentFilter`), or that contain `useEffect` hooks whose only purpose is to mirror another component's prop or state.
|
|
899
|
+
|
|
900
|
+
---
|
|
901
|
+
|
|
902
|
+
### AP-18: Too Many useState Variables (Should be useReducer)
|
|
903
|
+
|
|
904
|
+
**Also known as:** useState explosion, fragmented state, scattered state variables
|
|
905
|
+
**Frequency:** Common
|
|
906
|
+
**Severity:** Medium
|
|
907
|
+
**Detection difficulty:** Moderate
|
|
908
|
+
|
|
909
|
+
**What it looks like:**
|
|
910
|
+
```jsx
|
|
911
|
+
function CheckoutForm() {
|
|
912
|
+
const [name, setName] = useState('');
|
|
913
|
+
const [email, setEmail] = useState('');
|
|
914
|
+
const [address, setAddress] = useState('');
|
|
915
|
+
const [cardNumber, setCardNumber] = useState('');
|
|
916
|
+
const [cvv, setCvv] = useState('');
|
|
917
|
+
const [loading, setLoading] = useState(false);
|
|
918
|
+
const [error, setError] = useState(null);
|
|
919
|
+
const [step, setStep] = useState(1);
|
|
920
|
+
const [promoCode, setPromoCode] = useState('');
|
|
921
|
+
const [discount, setDiscount] = useState(0);
|
|
922
|
+
// ...6 more
|
|
923
|
+
}
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
**Why developers do it:**
|
|
927
|
+
`useState` is simpler to learn and reach for. The explosion happens gradually — each addition feels like adding one variable.
|
|
928
|
+
|
|
929
|
+
**What goes wrong:**
|
|
930
|
+
- Coordinated state transitions become error-prone: when submitting, you must remember to set `loading = true`, `error = null`, and clear fields simultaneously — miss one and the UI is inconsistent
|
|
931
|
+
- Invalid intermediate states become representable (e.g., `loading = true` and `error = 'Network timeout'` simultaneously)
|
|
932
|
+
- Reset logic must enumerate every variable individually
|
|
933
|
+
- Test setup for the component requires constructing many independent initial values
|
|
934
|
+
|
|
935
|
+
**The fix:**
|
|
936
|
+
```jsx
|
|
937
|
+
const initialState = {
|
|
938
|
+
fields: { name: '', email: '', address: '', cardNumber: '', cvv: '' },
|
|
939
|
+
ui: { loading: false, error: null, step: 1 },
|
|
940
|
+
promo: { code: '', discount: 0 },
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
function reducer(state, action) {
|
|
944
|
+
switch (action.type) {
|
|
945
|
+
case 'SUBMIT_START':
|
|
946
|
+
return { ...state, ui: { ...state.ui, loading: true, error: null } };
|
|
947
|
+
case 'SUBMIT_SUCCESS':
|
|
948
|
+
return { ...state, ui: { loading: false, error: null, step: state.ui.step + 1 } };
|
|
949
|
+
case 'SUBMIT_ERROR':
|
|
950
|
+
return { ...state, ui: { ...state.ui, loading: false, error: action.payload } };
|
|
951
|
+
default:
|
|
952
|
+
return state;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
**Detection rule:**
|
|
960
|
+
Flag any component containing more than 5 `useState` declarations where the state variables share a logical domain (form fields, async status, wizard steps).
|
|
961
|
+
|
|
962
|
+
---
|
|
963
|
+
|
|
964
|
+
### AP-19: Stale Closures Over State
|
|
965
|
+
|
|
966
|
+
**Also known as:** Stale closure bug, captured stale value, hook dependency omission
|
|
967
|
+
**Frequency:** Common
|
|
968
|
+
**Severity:** High
|
|
969
|
+
**Detection difficulty:** Hard
|
|
970
|
+
|
|
971
|
+
**What it looks like:**
|
|
972
|
+
```jsx
|
|
973
|
+
function Counter() {
|
|
974
|
+
const [count, setCount] = useState(0);
|
|
975
|
+
|
|
976
|
+
useEffect(() => {
|
|
977
|
+
const interval = setInterval(() => {
|
|
978
|
+
setCount(count + 1); // captures count=0 from mount closure
|
|
979
|
+
}, 1000);
|
|
980
|
+
return () => clearInterval(interval);
|
|
981
|
+
}, []); // empty deps — count is never refreshed
|
|
982
|
+
}
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
**Why developers do it:**
|
|
986
|
+
Developers add `[]` to `useEffect` to mean "run once on mount", correctly for setup — but when the effect's callback references state, the closure freezes the value at the time of creation. This is a non-obvious consequence of how JavaScript closures work.
|
|
987
|
+
|
|
988
|
+
**What goes wrong:**
|
|
989
|
+
- `count` is always `0` inside the interval callback; the counter increments from 0 to 1 and then freezes
|
|
990
|
+
- Similar bugs appear in `useCallback` with stale `props`, in `setTimeout` handlers, and in event listeners registered once
|
|
991
|
+
- The bug is invisible in tests that don't advance time and in development under StrictMode if intervals are manually re-registered
|
|
992
|
+
|
|
993
|
+
**The fix:**
|
|
994
|
+
```jsx
|
|
995
|
+
// Option A: use the functional updater form (avoids reading state in closure)
|
|
996
|
+
useEffect(() => {
|
|
997
|
+
const interval = setInterval(() => {
|
|
998
|
+
setCount(prev => prev + 1); // reads current state, not closed-over value
|
|
999
|
+
}, 1000);
|
|
1000
|
+
return () => clearInterval(interval);
|
|
1001
|
+
}, []);
|
|
1002
|
+
|
|
1003
|
+
// Option B: add count to deps (re-registers interval on every change)
|
|
1004
|
+
useEffect(() => {
|
|
1005
|
+
const interval = setInterval(() => setCount(count + 1), 1000);
|
|
1006
|
+
return () => clearInterval(interval);
|
|
1007
|
+
}, [count]);
|
|
1008
|
+
|
|
1009
|
+
// Option C: use useRef to hold a mutable callback (advanced)
|
|
1010
|
+
const savedCallback = useRef();
|
|
1011
|
+
useEffect(() => { savedCallback.current = () => setCount(c => c + 1); });
|
|
1012
|
+
useEffect(() => {
|
|
1013
|
+
const tick = () => savedCallback.current();
|
|
1014
|
+
const id = setInterval(tick, 1000);
|
|
1015
|
+
return () => clearInterval(id);
|
|
1016
|
+
}, []);
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
**Detection rule:**
|
|
1020
|
+
The `eslint-plugin-react-hooks` `exhaustive-deps` rule catches this automatically — flag any suppression comment (`// eslint-disable-next-line`) on a `useEffect`, `useCallback`, or `useMemo` dependency array.
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
### AP-20: Ignoring React.StrictMode Warnings
|
|
1025
|
+
|
|
1026
|
+
**Also known as:** StrictMode suppression, double-invoke blind spot, silenced dev warnings
|
|
1027
|
+
**Frequency:** Common
|
|
1028
|
+
**Severity:** Medium
|
|
1029
|
+
**Detection difficulty:** Easy
|
|
1030
|
+
|
|
1031
|
+
**What it looks like:**
|
|
1032
|
+
```jsx
|
|
1033
|
+
// Common "fix": remove StrictMode to silence the double-invoke noise
|
|
1034
|
+
// Root file:
|
|
1035
|
+
root.render(
|
|
1036
|
+
// <React.StrictMode> <-- removed because "it caused bugs"
|
|
1037
|
+
<App />
|
|
1038
|
+
// </React.StrictMode>
|
|
1039
|
+
);
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
**Why developers do it:**
|
|
1043
|
+
`React.StrictMode` double-invokes render functions and effects in development to expose impure renders and missing cleanups. When this surfaces bugs, developers often interpret the double-invoke as the cause rather than the revealer and remove `StrictMode`.
|
|
1044
|
+
|
|
1045
|
+
**What goes wrong:**
|
|
1046
|
+
- `StrictMode` removal hides real bugs that will manifest in React 18+ Concurrent Mode features (Suspense, transitions, deferred rendering), which legitimately re-invoke renders and effects
|
|
1047
|
+
- The underlying bugs (impure renders, missing cleanup, mutated state) persist and surface in production under real concurrent rendering conditions
|
|
1048
|
+
- Future upgrades to React become vastly more painful
|
|
1049
|
+
|
|
1050
|
+
**Common warnings and their root causes:**
|
|
1051
|
+
|
|
1052
|
+
| Warning | Root Cause |
|
|
1053
|
+
|---|---|
|
|
1054
|
+
| "Cannot update a component while rendering" | State update triggered during another component's render |
|
|
1055
|
+
| "Each child in a list should have a unique key" | AP-03 |
|
|
1056
|
+
| "Can't perform state update on unmounted component" | AP-07 or AP-12 |
|
|
1057
|
+
| "A component is changing uncontrolled to controlled" | AP-16 |
|
|
1058
|
+
| Double network requests in dev | Missing cleanup in AP-07 / AP-12 |
|
|
1059
|
+
|
|
1060
|
+
**The fix:**
|
|
1061
|
+
Keep `<React.StrictMode>` enabled at all times during development. Treat every warning it surfaces as a bug to fix, not noise to suppress. Fix missing cleanups (AP-07), impure renders (AP-04), and unguarded effects (AP-12) rather than disabling the detector.
|
|
1062
|
+
|
|
1063
|
+
**Detection rule:**
|
|
1064
|
+
Flag any `createRoot().render()` or `ReactDOM.render()` call that does not wrap the root in `<React.StrictMode>`. Flag any git commit that removes `StrictMode` without a documented architectural reason.
|
|
1065
|
+
|
|
1066
|
+
---
|
|
1067
|
+
|
|
1068
|
+
## Root Cause Analysis
|
|
1069
|
+
|
|
1070
|
+
The 20 anti-patterns above cluster into six underlying causes:
|
|
1071
|
+
|
|
1072
|
+
| Root Cause | Anti-Patterns | Core Problem |
|
|
1073
|
+
|---|---|---|
|
|
1074
|
+
| Mental model mismatch | AP-02, AP-06, AP-19 | Treating React's render model as a reactive event system or imperative loop |
|
|
1075
|
+
| JavaScript primitive behaviour | AP-03, AP-04, AP-09 | Reference equality, array mutability, and closure capture are not React-specific |
|
|
1076
|
+
| Incremental growth | AP-10, AP-17, AP-18 | Patterns that are correct at small scale become anti-patterns at scale |
|
|
1077
|
+
| Missing lifecycle knowledge | AP-07, AP-12, AP-20 | Incomplete understanding of effect cleanup and StrictMode's purpose |
|
|
1078
|
+
| Security / safety omission | AP-14, AP-15 | Features that require explicit opt-in to be safe |
|
|
1079
|
+
| Performance misapplication | AP-08, AP-09, AP-11 | Memoisation and context tools used without understanding their cost model |
|
|
1080
|
+
|
|
1081
|
+
---
|
|
1082
|
+
|
|
1083
|
+
## Self-Check Questions
|
|
1084
|
+
|
|
1085
|
+
Use these during code review or self-review to catch the anti-patterns above before they reach production.
|
|
1086
|
+
|
|
1087
|
+
1. Does any component receive a prop it never reads — only passes to a child? *(AP-01)*
|
|
1088
|
+
2. Is there a `useState` + `useEffect` pair where the only thing the effect does is call `setState` with a value derived from the dependencies? *(AP-02, AP-06)*
|
|
1089
|
+
3. Are all list keys stable unique identifiers from the data domain, not array indices? *(AP-03)*
|
|
1090
|
+
4. Are `.push()`, `.splice()`, or direct property assignments ever called on values from `useState`? *(AP-04)*
|
|
1091
|
+
5. Are any components (starting with an uppercase letter, returning JSX) declared inside the body of another component function? *(AP-05)*
|
|
1092
|
+
6. Does every `useEffect` that opens a subscription, sets a timer, or starts a fetch return a cleanup function? *(AP-07, AP-12)*
|
|
1093
|
+
7. Is every `useMemo` and `useCallback` accompanied by a measurable reason and consumed by a `React.memo`-wrapped component or a hook dependency? *(AP-08, AP-09)*
|
|
1094
|
+
8. Does any component exceed 150–200 lines or contain more than 5 `useState` hooks? *(AP-10, AP-18)*
|
|
1095
|
+
9. Is context split by change frequency — fast-changing values isolated from slow-changing ones? *(AP-11)*
|
|
1096
|
+
10. Do all data-fetching components render distinct UI for loading, error, and empty states? *(AP-13)*
|
|
1097
|
+
11. Is every use of `dangerouslySetInnerHTML` sanitised with DOMPurify or an equivalent library? *(AP-14)*
|
|
1098
|
+
12. Is there at least one `ErrorBoundary` at the app root and around each independently deployable UI section? *(AP-15)*
|
|
1099
|
+
13. Can any controlled input's `value` prop evaluate to `undefined` or `null`? *(AP-16)*
|
|
1100
|
+
14. Do sibling components manage state that they need to share — requiring an effect to keep them in sync? *(AP-17)*
|
|
1101
|
+
15. Is `React.StrictMode` enabled in all development builds, and are its warnings treated as bugs? *(AP-20)*
|
|
1102
|
+
|
|
1103
|
+
---
|
|
1104
|
+
|
|
1105
|
+
## Code Smell Quick Reference
|
|
1106
|
+
|
|
1107
|
+
| Smell | Likely Anti-Pattern | Severity |
|
|
1108
|
+
|---|---|---|
|
|
1109
|
+
| `key={index}` in a `.map()` | AP-03 | High |
|
|
1110
|
+
| `useState` + `useEffect` that only calls `setState` | AP-02, AP-06 | High |
|
|
1111
|
+
| `useEffect` with no return value, contains `fetch()` | AP-07, AP-12 | High |
|
|
1112
|
+
| `const Component = () =>` inside a component body | AP-05 | High |
|
|
1113
|
+
| `dangerouslySetInnerHTML` without sanitisation call | AP-14 | High |
|
|
1114
|
+
| `items.push(...)` followed by `setItems(items)` | AP-04 | High |
|
|
1115
|
+
| Object literal `{}` or arrow function as prop on `React.memo` component | AP-09 | High |
|
|
1116
|
+
| No `ErrorBoundary` wrapping route or root | AP-15 | High |
|
|
1117
|
+
| 6+ `useState` declarations in one component | AP-10, AP-18 | Medium |
|
|
1118
|
+
| Props passed through 3+ layers that an intermediate component ignores | AP-01 | Medium |
|
|
1119
|
+
| `<React.StrictMode>` absent or commented out | AP-20 | Medium |
|
|
1120
|
+
| `useMemo` on a simple expression (string, number, single property) | AP-08 | Low |
|
|
1121
|
+
| Context provider whose `value` is an inline object `{{ a, b, c, d, e }}` | AP-11 | High |
|
|
1122
|
+
| `value={someVar}` input with no fallback for `undefined` | AP-16 | Medium |
|
|
1123
|
+
| No loading/error/empty branch in a component that fetches | AP-13 | Medium |
|
|
1124
|
+
| `setInterval`/`setTimeout` in `useEffect` with `[]` deps and state read inside callback | AP-19 | High |
|
|
1125
|
+
|
|
1126
|
+
---
|
|
1127
|
+
|
|
1128
|
+
*Researched: 2026-03-08 | Sources: [React — You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect), [Kent C. Dodds — Prop Drilling](https://kentcdodds.com/blog/prop-drilling), [Kent C. Dodds — When to useMemo and useCallback](https://kentcdodds.com/blog/usememo-and-usecallback), [Dmitri Pavlutin — Stale Closures in React Hooks](https://dmitripavlutin.com/react-hooks-stale-closures/), [LogRocket — 15 common useEffect mistakes](https://blog.logrocket.com/15-common-useeffect-mistakes-react/), [Nadia Makarevich — How to useMemo and useCallback: you can remove most of them](https://www.developerway.com/posts/how-to-use-memo-use-callback), [React Hooks Anti-Patterns — Tech Insights](https://techinsights.manisuec.com/reactjs/react-hooks-antipatterns/), [React 19 Compiler and memoisation](https://isitdev.com/react-19-compiler-usememo-usecallback-2025/), [React XSS and dangerouslySetInnerHTML — StackHawk](https://www.stackhawk.com/blog/react-xss-guide-examples-and-prevention/), [15 React Anti-Patterns — jsdev.space](https://jsdev.space/react-anti-patterns-2025/), [6 Common React Anti-Patterns — ITNEXT](https://itnext.io/6-common-react-anti-patterns-that-are-hurting-your-code-quality-904b9c32e933)]*
|