React Best Practices for Benefriches

SkillDev tools

React best practices for Benefriches (Vite + Redux). Covers code quality, component patterns, state management, and performance. Use when writing, reviewing, or refactoring React components, debugging slow interactions, or implementing Redux patterns.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the React Best Practices for Benefriches skill

What this skill tells your AI

The instructions your AI receives, as published by incubateur-ademe/benefriches in .claude/skills/react-best-practices/SKILL.md and read by ahel’s review.

Guidelines for React 19+ SPA with Vite + Redux

Philosophy: Code quality and maintainability first, performance optimization when measured

Adapted for: Client-side rendering with Redux + Clean Architecture


When to Apply This Skill

Use these practices when:

  • Writing new React components
  • Designing component architecture
  • Implementing Redux patterns (reducers, selectors, thunks)
  • Reviewing code for quality or performance issues
  • Refactoring existing React code
  • Debugging slow interactions

Categories by Priority

PriorityCategoryFocus Area
🔴Code QualityReadability, maintainability, SRP
🟠Component PatternsContainer/Presentational, composition
🟡State ManagementLocal-first, derived state, colocation
🟢Anti-PatternsCommon mistakes to avoid
🔵Bundle OptimizationLazy loading, dynamic imports
🟣Async PatternsParallel fetching, Suspense
🟤Form Handlingreact-hook-form patterns, DSFR
AccessibilityKeyboard nav, ARIA, focus management
Error BoundariesCatch errors, prevent app crashes
Performance (Measure!)Only when needed, after profiling
React 19 & FutureReact Compiler, new APIs

🔴 CRITICAL: Code Quality & Readability

PracticeDescription
Single ResponsibilityEach component does ONE thing well
Component SizeKeep components focused (< 200 lines)
Descriptive NamingClear names for components, hooks, props
Props DestructuringImprove readability at function signature
Explicit over ImplicitAvoid magic values, use named constants
Extract Custom HooksShare logic via hooks, not copy-paste

Benefriches Examples

  • ViewData pattern: Single selector per container
  • Container/Presentational: Separation in views/ folders
  • Clean Architecture: Core has no framework dependencies

🟠 HIGH: Component Design Patterns

PatternWhen to Use
Container/PresentationalRedux connection in index.tsx, pure render
Component CompositionPrefer over deep prop drilling
Children PatternFlexible content injection
Custom HooksExtract reusable stateful logic
Render Props (rare)Dynamic child rendering needs

Benefriches Already Follows

  • ✅ Container components use single selectViewData selector
  • ✅ Presentational components receive all data via props
  • ✅ Gateway pattern for external services

🟡 HIGH: State Management Principles

PrincipleDescription
Local State FirstDon't lift state unless truly shared
Derived StateCompute in selectors/render, don't store
Colocate StateKeep state close to where it's used
Single SourceOne authoritative location per piece of data
ImmutabilityAlways use toSorted(), spread, not sort()

Redux Specifics

  • ✅ Derived values in selectors (not duplicated in state)
  • ✅ Functional updates in reducers
  • ✅ Single ViewData selector per container

🟢 HIGH: Anti-Patterns to Avoid

Anti-PatternProblemSolution
Massive ComponentsHard to test/maintainSplit into focused pieces
Prop DrillingCoupling, maintenanceUse composition or context
Array Index as KeyBugs with reorderingUse stable IDs
Mutating StateReact won't re-renderImmutable updates (toSorted())
Over-EngineeringComplexity without benefitYAGNI - only what's needed
Premature OptimizationWasted effortMeasure first, then optimize
Effect for Derived StateSync issues, extra rendersCompute during render

🔵 MEDIUM: Bundle Optimization

PracticeImpactWhen to Apply
Avoid Barrel File Imports200-800ms reductionUse direct @/ path imports
Dynamic Imports (lazy)Reduce initial bundleMaps, charts, modals, forms
Defer Non-Critical LibrariesFaster initial loadAnalytics, error tracking
Preload on User IntentReduce perceived delayHover/focus before heavy action

🟣 MEDIUM: Async Patterns

PracticeImpactWhen to Apply
Promise.all() Parallel2-10x improvementIndependent async operations
Defer Await Until NeededSkip wasted workConditional logic before fetch
Strategic SuspenseProgressive loadingWrap data-dependent sections
Conditional Module LoadingOn-demand bundlesCharts, PDFs, advanced features

🟤 MEDIUM: Form Handling

PracticeDescription
react-hook-formPreferred library for all forms
DSFR ComponentsUse @codegouvfr/react-dsfr for inputs
Validation in SchemaUse react-hook-form validation rules
Error State DisplayMap formState.errors to DSFR error states
Controlled InputsPrefer controlled via register()

Benefriches Form Pattern

// Standard form component pattern
import { useForm } from "react-hook-form";
import { Input } from "@codegouvfr/react-dsfr/Input";

type FormValues = { name: string; email: string };

function MyForm({ onSubmit }: { onSubmit: (data: FormValues) => void }) {
  const { register, handleSubmit, formState } = useForm<FormValues>();

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Input
        label="Email"
        state={formState.errors.email ? "error" : "default"}
        stateRelatedMessage={formState.errors.email?.message}
        nativeInputProps={{
          ...register("email", {
            required: "Email requis",
            pattern: { value: /^[^@]+@[^@]+$/, message: "Email invalide" },
          }),
        }}
      />
    </form>
  );
}

Reference Files

  • src/features/onboarding/views/pages/identity/CreateUserForm/CreateUserForm.tsx
  • src/features/create-site/views/custom/naming/SiteNameAndDescription.tsx

⬜ MEDIUM: Accessibility

PracticeDescription
Semantic HTMLUse appropriate elements (button, nav, main)
ARIA LabelsAdd when semantic HTML isn't sufficient
Keyboard NavigationSupport Tab, Enter, Escape for interactive UI
Focus ManagementManage focus for modals and dynamic content
Icon AccessibilityUse aria-hidden="true" for decorative icons

Keyboard Navigation Example

// Handle Escape key in modals/dropdowns
function Modal({ onClose, children }) {
  useEffect(() => {
    const handleEscape = (e: KeyboardEvent) => {
      if (e.key === "Escape") onClose();
    };
    document.addEventListener("keydown", handleEscape);
    return () => document.removeEventListener("keydown", handleEscape);
  }, [onClose]);

  return <div role="dialog" aria-modal="true">{children}</div>;
}

Icon Accessibility

// Decorative icons should be hidden from screen readers
<i className="fr-icon-check-line" aria-hidden="true" />

// Informative icons need labels
<button aria-label="Fermer">
  <i className="fr-icon-close-line" aria-hidden="true" />
</button>

DSFR Provides Accessibility

DSFR components handle most accessibility concerns. Rely on:

  • Built-in ARIA attributes in DSFR components
  • Proper focus management in modals via createModal()
  • Keyboard support in form controls

⬛ CONSIDER: Error Boundaries

Error boundaries catch JavaScript errors in component trees and display fallback UI.

When to UseExample
Async data sectionsWrap data-fetching components
Third-party componentsIsolate potentially failing libraries
Feature boundariesPrevent one feature from crashing app

Basic Pattern

import { Component, ErrorInfo, ReactNode } from "react";

type Props = { children: ReactNode; fallback: ReactNode };
type State = { hasError: boolean };

class ErrorBoundary extends Component<Props, State> {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    console.error("Error boundary caught:", error, info);
  }

  render() {
    return this.state.hasError ? this.props.fallback : this.props.children;
  }
}

// Usage
<ErrorBoundary fallback={<p>Une erreur est survenue</p>}>
  <RiskyComponent />
</ErrorBoundary>

Note: Not yet implemented in Benefriches. Consider adding for critical sections.


⚫ LOW: Performance Optimization (Measure First!)

CRITICAL: Only apply these when you've measured a performance problem.

Memoization: Usually NOT Needed

Default stance: Don't memoize. It adds complexity without benefit in most cases.

When NOT to MemoizeWhy
Props change every renderMemoization is wasted
Component is already fastNo perceptible benefit
Simple componentsOverhead may exceed savings
Object/array literals as propsCreates new reference each render
When to Consider MemoizationRequirements
Measured lag during re-rendersProfile first!
Expensive rendering (long lists)And props rarely change
Heavy computations in renderAnd dependencies stable

Better Alternatives to Memoization

  1. Move state down: Keep state in component that needs it
  2. Lift content up: Use children pattern for static content
  3. Component composition: Split into smaller, focused pieces
  4. Selector optimization: Derive booleans in selectors

React Compiler (Coming Soon)

React Compiler will auto-memoize, making manual useMemo, useCallback, and React.memo largely redundant. Avoid adding new memoization unless solving a measured problem.


⚪ React 19 & Future

FeatureImpact
React CompilerAuto-memoization (manual memo becomes legacy)
useTransitionNon-blocking UI updates for heavy operations
use() hookSimplified async data fetching

Benefriches-Specific Integration

Redux Patterns

Already following best practices:

  • ✅ Derived state in selectors (not duplicated)
  • ✅ Single ViewData selector per container
  • ✅ Functional updates in reducers
  • toSorted() for immutability

Keep doing:

  • 🟡 Single selector per container returning composed ViewData
  • 🔴 Parallel async in thunks with Promise.all()
  • 🟢 Passive action names (events: stepCompleted, not commands)

Clean Architecture

  • Core layer: Pure functions, no framework deps
  • Infrastructure layer: Gateways with InMemory mocks for tests
  • Views layer: Container/Presentational separation

Path Aliases

  • 🔴 Use @/ for imports - avoid barrel files
  • Example: import { X } from '@/features/create-site/core/createSite.reducer'

See Also

  • Code examples: examples.md in this skill directory
  • Web app guide: apps/web/CLAUDE.md
  • Monorepo guide: Root CLAUDE.md

END OF QUICK REFERENCE - For code examples and detailed patterns, see examples.md.

Signals

GitHub stars
45
Forks
3
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
react-best-practices-incubateur-ademe
Source
github.com/incubateur-ademe/benefriches