React Best Practices for Benefriches
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
| Priority |
Category |
Focus Area |
| 🔴 |
Code Quality |
Readability, maintainability, SRP |
| 🟠 |
Component Patterns |
Container/Presentational, composition |
| 🟡 |
State Management |
Local-first, derived state, colocation |
| 🟢 |
Anti-Patterns |
Common mistakes to avoid |
| 🔵 |
Bundle Optimization |
Lazy loading, dynamic imports |
| 🟣 |
Async Patterns |
Parallel fetching, Suspense |
| 🟤 |
Form Handling |
react-hook-form patterns, DSFR |
| ⬜ |
Accessibility |
Keyboard nav, ARIA, focus management |
| ⬛ |
Error Boundaries |
Catch errors, prevent app crashes |
| ⚫ |
Performance (Measure!) |
Only when needed, after profiling |
| ⚪ |
React 19 & Future |
React Compiler, new APIs |
🔴 CRITICAL: Code Quality & Readability
| Practice |
Description |
| Single Responsibility |
Each component does ONE thing well |
| Component Size |
Keep components focused (< 200 lines) |
| Descriptive Naming |
Clear names for components, hooks, props |
| Props Destructuring |
Improve readability at function signature |
| Explicit over Implicit |
Avoid magic values, use named constants |
| Extract Custom Hooks |
Share 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
| Pattern |
When to Use |
| Container/Presentational |
Redux connection in index.tsx, pure render |
| Component Composition |
Prefer over deep prop drilling |
| Children Pattern |
Flexible content injection |
| Custom Hooks |
Extract 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
| Principle |
Description |
| Local State First |
Don't lift state unless truly shared |
| Derived State |
Compute in selectors/render, don't store |
| Colocate State |
Keep state close to where it's used |
| Single Source |
One authoritative location per piece of data |
| Immutability |
Always 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-Pattern |
Problem |
Solution |
| Massive Components |
Hard to test/maintain |
Split into focused pieces |
| Prop Drilling |
Coupling, maintenance |
Use composition or context |
| Array Index as Key |
Bugs with reordering |
Use stable IDs |
| Mutating State |
React won't re-render |
Immutable updates (toSorted()) |
| Over-Engineering |
Complexity without benefit |
YAGNI - only what's needed |
| Premature Optimization |
Wasted effort |
Measure first, then optimize |
| Effect for Derived State |
Sync issues, extra renders |
Compute during render |
🔵 MEDIUM: Bundle Optimization
| Practice |
Impact |
When to Apply |
| Avoid Barrel File Imports |
200-800ms reduction |
Use direct @/ path imports |
Dynamic Imports (lazy) |
Reduce initial bundle |
Maps, charts, modals, forms |
| Defer Non-Critical Libraries |
Faster initial load |
Analytics, error tracking |
| Preload on User Intent |
Reduce perceived delay |
Hover/focus before heavy action |
🟣 MEDIUM: Async Patterns
| Practice |
Impact |
When to Apply |
Promise.all() Parallel |
2-10x improvement |
Independent async operations |
| Defer Await Until Needed |
Skip wasted work |
Conditional logic before fetch |
| Strategic Suspense |
Progressive loading |
Wrap data-dependent sections |
| Conditional Module Loading |
On-demand bundles |
Charts, PDFs, advanced features |
🟤 MEDIUM: Form Handling
| Practice |
Description |
| react-hook-form |
Preferred library for all forms |
| DSFR Components |
Use @codegouvfr/react-dsfr for inputs |
| Validation in Schema |
Use react-hook-form validation rules |
| Error State Display |
Map formState.errors to DSFR error states |
| Controlled Inputs |
Prefer 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
<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
| Practice |
Description |
| Semantic HTML |
Use appropriate elements (button, nav, main) |
| ARIA Labels |
Add when semantic HTML isn't sufficient |
| Keyboard Navigation |
Support Tab, Enter, Escape for interactive UI |
| Focus Management |
Manage focus for modals and dynamic content |
| Icon Accessibility |
Use 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 Use |
Example |
| Async data sections |
Wrap data-fetching components |
| Third-party components |
Isolate potentially failing libraries |
| Feature boundaries |
Prevent 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 Memoize |
Why |
| Props change every render |
Memoization is wasted |
| Component is already fast |
No perceptible benefit |
| Simple components |
Overhead may exceed savings |
| Object/array literals as props |
Creates new reference each render |
| When to Consider Memoization |
Requirements |
| Measured lag during re-renders |
Profile first! |
| Expensive rendering (long lists) |
And props rarely change |
| Heavy computations in render |
And dependencies stable |
Better Alternatives to Memoization
- Move state down: Keep state in component that needs it
- Lift content up: Use children pattern for static content
- Component composition: Split into smaller, focused pieces
- 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
| Feature |
Impact |
| React Compiler |
Auto-memoization (manual memo becomes legacy) |
useTransition |
Non-blocking UI updates for heavy operations |
use() hook |
Simplified 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.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: react-best-practices-163description: 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. Use when this capability is needed.4---56# React Best Practices for Benefriches78> **Guidelines for React 19+ SPA with Vite + Redux**9>10> **Philosophy**: Code quality and maintainability first, performance optimization when measured11>12> **Adapted for**: Client-side rendering with Redux + Clean Architecture1314---1516## When to Apply This Skill1718Use these practices when:1920- Writing new React components21- Designing component architecture22- Implementing Redux patterns (reducers, selectors, thunks)23- Reviewing code for quality or performance issues24- Refactoring existing React code25- Debugging slow interactions2627---2829## Categories by Priority3031| Priority | Category | Focus Area |32| -------- | ------------------------ | ------------------------------------- |33| 🔴 | **Code Quality** | Readability, maintainability, SRP |34| 🟠 | **Component Patterns** | Container/Presentational, composition |35| 🟡 | **State Management** | Local-first, derived state, colocation|36| 🟢 | **Anti-Patterns** | Common mistakes to avoid |37| 🔵 | **Bundle Optimization** | Lazy loading, dynamic imports |38| 🟣 | **Async Patterns** | Parallel fetching, Suspense |39| 🟤 | **Form Handling** | react-hook-form patterns, DSFR |40| ⬜ | **Accessibility** | Keyboard nav, ARIA, focus management |41| ⬛ | **Error Boundaries** | Catch errors, prevent app crashes |42| ⚫ | **Performance (Measure!)**| Only when needed, after profiling |43| ⚪ | **React 19 & Future** | React Compiler, new APIs |4445---4647## 🔴 CRITICAL: Code Quality & Readability4849| Practice | Description |50| ---------------------------- | ------------------------------------------------ |51| Single Responsibility | Each component does ONE thing well |52| Component Size | Keep components focused (< 200 lines) |53| Descriptive Naming | Clear names for components, hooks, props |54| Props Destructuring | Improve readability at function signature |55| Explicit over Implicit | Avoid magic values, use named constants |56| Extract Custom Hooks | Share logic via hooks, not copy-paste |5758### Benefriches Examples5960- ✅ **ViewData pattern**: Single selector per container61- ✅ **Container/Presentational**: Separation in `views/` folders62- ✅ **Clean Architecture**: Core has no framework dependencies6364---6566## 🟠 HIGH: Component Design Patterns6768| Pattern | When to Use |69| ------------------------ | -------------------------------------------- |70| Container/Presentational | Redux connection in `index.tsx`, pure render |71| Component Composition | Prefer over deep prop drilling |72| Children Pattern | Flexible content injection |73| Custom Hooks | Extract reusable stateful logic |74| Render Props (rare) | Dynamic child rendering needs |7576### Benefriches Already Follows7778- ✅ Container components use single `selectViewData` selector79- ✅ Presentational components receive all data via props80- ✅ Gateway pattern for external services8182---8384## 🟡 HIGH: State Management Principles8586| Principle | Description |87| --------------------- | -------------------------------------------------- |88| Local State First | Don't lift state unless truly shared |89| Derived State | Compute in selectors/render, don't store |90| Colocate State | Keep state close to where it's used |91| Single Source | One authoritative location per piece of data |92| Immutability | Always use `toSorted()`, spread, not `sort()` |9394### Redux Specifics9596- ✅ Derived values in selectors (not duplicated in state)97- ✅ Functional updates in reducers98- ✅ Single ViewData selector per container99100---101102## 🟢 HIGH: Anti-Patterns to Avoid103104| Anti-Pattern | Problem | Solution |105| ------------------------- | -------------------------- | -------------------------------- |106| Massive Components | Hard to test/maintain | Split into focused pieces |107| Prop Drilling | Coupling, maintenance | Use composition or context |108| Array Index as Key | Bugs with reordering | Use stable IDs |109| Mutating State | React won't re-render | Immutable updates (`toSorted()`) |110| Over-Engineering | Complexity without benefit | YAGNI - only what's needed |111| Premature Optimization | Wasted effort | Measure first, then optimize |112| Effect for Derived State | Sync issues, extra renders | Compute during render |113114---115116## 🔵 MEDIUM: Bundle Optimization117118| Practice | Impact | When to Apply |119| ---------------------------- | ----------------------- | ----------------------------------- |120| Avoid Barrel File Imports | 200-800ms reduction | Use direct `@/` path imports |121| Dynamic Imports (`lazy`) | Reduce initial bundle | Maps, charts, modals, forms |122| Defer Non-Critical Libraries | Faster initial load | Analytics, error tracking |123| Preload on User Intent | Reduce perceived delay | Hover/focus before heavy action |124125---126127## 🟣 MEDIUM: Async Patterns128129| Practice | Impact | When to Apply |130| -------------------------- | ------------------- | -------------------------------- |131| `Promise.all()` Parallel | 2-10x improvement | Independent async operations |132| Defer Await Until Needed | Skip wasted work | Conditional logic before fetch |133| Strategic Suspense | Progressive loading | Wrap data-dependent sections |134| Conditional Module Loading | On-demand bundles | Charts, PDFs, advanced features |135136---137138## 🟤 MEDIUM: Form Handling139140| Practice | Description |141| ---------------------------- | ------------------------------------------------ |142| react-hook-form | Preferred library for all forms |143| DSFR Components | Use @codegouvfr/react-dsfr for inputs |144| Validation in Schema | Use react-hook-form validation rules |145| Error State Display | Map formState.errors to DSFR error states |146| Controlled Inputs | Prefer controlled via `register()` |147148### Benefriches Form Pattern149150```typescript151// Standard form component pattern152import { useForm } from "react-hook-form";153import { Input } from "@codegouvfr/react-dsfr/Input";154155type FormValues = { name: string; email: string };156157function MyForm({ onSubmit }: { onSubmit: (data: FormValues) => void }) {158 const { register, handleSubmit, formState } = useForm<FormValues>();159160 return (161 <form onSubmit={handleSubmit(onSubmit)}>162 <Input163 label="Email"164 state={formState.errors.email ? "error" : "default"}165 stateRelatedMessage={formState.errors.email?.message}166 nativeInputProps={{167 ...register("email", {168 required: "Email requis",169 pattern: { value: /^[^@]+@[^@]+$/, message: "Email invalide" },170 }),171 }}172 />173 </form>174 );175}176```177178### Reference Files179180- `src/features/onboarding/views/pages/identity/CreateUserForm/CreateUserForm.tsx`181- `src/features/create-site/views/custom/naming/SiteNameAndDescription.tsx`182183---184185## ⬜ MEDIUM: Accessibility186187| Practice | Description |188| ---------------------- | ------------------------------------------------- |189| Semantic HTML | Use appropriate elements (button, nav, main) |190| ARIA Labels | Add when semantic HTML isn't sufficient |191| Keyboard Navigation | Support Tab, Enter, Escape for interactive UI |192| Focus Management | Manage focus for modals and dynamic content |193| Icon Accessibility | Use `aria-hidden="true"` for decorative icons |194195### Keyboard Navigation Example196197```typescript198// Handle Escape key in modals/dropdowns199function Modal({ onClose, children }) {200 useEffect(() => {201 const handleEscape = (e: KeyboardEvent) => {202 if (e.key === "Escape") onClose();203 };204 document.addEventListener("keydown", handleEscape);205 return () => document.removeEventListener("keydown", handleEscape);206 }, [onClose]);207208 return <div role="dialog" aria-modal="true">{children}</div>;209}210```211212### Icon Accessibility213214```typescript215// Decorative icons should be hidden from screen readers216<i className="fr-icon-check-line" aria-hidden="true" />217218// Informative icons need labels219<button aria-label="Fermer">220 <i className="fr-icon-close-line" aria-hidden="true" />221</button>222```223224### DSFR Provides Accessibility225226DSFR components handle most accessibility concerns. Rely on:227- Built-in ARIA attributes in DSFR components228- Proper focus management in modals via `createModal()`229- Keyboard support in form controls230231---232233## ⬛ CONSIDER: Error Boundaries234235Error boundaries catch JavaScript errors in component trees and display fallback UI.236237| When to Use | Example |238| ------------------------- | ---------------------------------------- |239| Async data sections | Wrap data-fetching components |240| Third-party components | Isolate potentially failing libraries |241| Feature boundaries | Prevent one feature from crashing app |242243### Basic Pattern244245```typescript246import { Component, ErrorInfo, ReactNode } from "react";247248type Props = { children: ReactNode; fallback: ReactNode };249type State = { hasError: boolean };250251class ErrorBoundary extends Component<Props, State> {252 state = { hasError: false };253254 static getDerivedStateFromError() {255 return { hasError: true };256 }257258 componentDidCatch(error: Error, info: ErrorInfo) {259 console.error("Error boundary caught:", error, info);260 }261262 render() {263 return this.state.hasError ? this.props.fallback : this.props.children;264 }265}266267// Usage268<ErrorBoundary fallback={<p>Une erreur est survenue</p>}>269 <RiskyComponent />270</ErrorBoundary>271```272273**Note**: Not yet implemented in Benefriches. Consider adding for critical sections.274275---276277## ⚫ LOW: Performance Optimization (Measure First!)278279**CRITICAL**: Only apply these when you've **measured** a performance problem.280281### Memoization: Usually NOT Needed282283**Default stance**: Don't memoize. It adds complexity without benefit in most cases.284285| When NOT to Memoize | Why |286| ---------------------------------- | ------------------------------------- |287| Props change every render | Memoization is wasted |288| Component is already fast | No perceptible benefit |289| Simple components | Overhead may exceed savings |290| Object/array literals as props | Creates new reference each render |291292| When to Consider Memoization | Requirements |293| ---------------------------------- | ------------------------------------- |294| Measured lag during re-renders | Profile first! |295| Expensive rendering (long lists) | And props rarely change |296| Heavy computations in render | And dependencies stable |297298### Better Alternatives to Memoization2993001. **Move state down**: Keep state in component that needs it3012. **Lift content up**: Use children pattern for static content3023. **Component composition**: Split into smaller, focused pieces3034. **Selector optimization**: Derive booleans in selectors304305### React Compiler (Coming Soon)306307React Compiler will auto-memoize, making manual `useMemo`, `useCallback`, and `React.memo` largely redundant. Avoid adding new memoization unless solving a measured problem.308309---310311## ⚪ React 19 & Future312313| Feature | Impact |314| -------------------- | --------------------------------------------- |315| React Compiler | Auto-memoization (manual memo becomes legacy) |316| `useTransition` | Non-blocking UI updates for heavy operations |317| `use()` hook | Simplified async data fetching |318319---320321## Benefriches-Specific Integration322323### Redux Patterns324325**Already following best practices:**326327- ✅ Derived state in selectors (not duplicated)328- ✅ Single ViewData selector per container329- ✅ Functional updates in reducers330- ✅ `toSorted()` for immutability331332**Keep doing:**333334- 🟡 **Single selector per container** returning composed ViewData335- 🔴 **Parallel async in thunks** with `Promise.all()`336- 🟢 **Passive action names** (events: `stepCompleted`, not commands)337338### Clean Architecture339340- **Core layer**: Pure functions, no framework deps341- **Infrastructure layer**: Gateways with InMemory mocks for tests342- **Views layer**: Container/Presentational separation343344### Path Aliases345346- 🔴 **Use `@/` for imports** - avoid barrel files347- Example: `import { X } from '@/features/create-site/core/createSite.reducer'`348349---350351## See Also352353- **Code examples**: [examples.md](examples.md) in this skill directory354- **Web app guide**: `apps/web/CLAUDE.md`355- **Monorepo guide**: Root `CLAUDE.md`356357---358359**END OF QUICK REFERENCE** - For code examples and detailed patterns, see [examples.md](examples.md).360361---362> Converted and distributed by [TomeVault](https://tomevault.io/claim/incubateur-ademe) — claim your Tome and manage your conversions.363<!-- tomevault:4.0:skill_md:2026-04-11 -->