Skip to main content

react

React patterns - component architecture, state management (Zustand, XState), hook composition, and render performance. Use when building React components, managing state with Zustand, composing hooks, or diagnosing re-render performance issues.

Informations de source

Dépôt
greenpill-dev-guild/coop
Dernière activité de la source
5 avril 2026 à 06:26
Langue détectée de SKILL.md
anglais
Étoiles
3
Forks
1

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
4 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
react
description
React patterns - component architecture, state management (Zustand, XState), hook composition, and render performance. Use when building React components, managing state with Zustand, composing hooks, or diagnosing re-render performance issues.
version
1.0.0
status
active
packages
["shared","app","extension"]
dependencies
[]
last_updated
2026-03-12
last_verified
2026-03-12
# React Skill Complete React guide: state management, component composition, and performance optimization. --- ## Activation When invoked: - Identify state category (local/global/server/form/flow) before choosing a tool. - Keep hooks in `@coop/shared` only. - Use granular Zustand selectors to limit re-renders. ## Part 1: State Management ### State Categories | Type | Description | Solutions | |------|-------------|-----------| | **Local State** | Component-specific UI | useState, useReducer | | **Global State** | Shared across components | Zustand (Coop default) | | **Local-First Data** | Dexie + Yjs persistence | Dexie live queries, Yjs observeDeep | | **URL State** | Route params, search | React Router | | **Form State** | Input values, validation | React Hook Form + Zod | | **Complex Flows** | Multi-step workflows, state machines | XState (Coop flows/) | **Coop uses:** Zustand + XState + Dexie + Yjs ### Zustand Patterns #### Basic Store ```typescript // packages/shared/src/stores/appStore.ts import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; interface AppState { user: User | null; theme: "light" | "dark"; setUser: (user: User | null) => void; toggleTheme: () => void; } export const useAppStore = create<AppState>()( devtools( persist( (set) => ({ user: null, theme: "light", setUser: (user) => set({ user }), toggleTheme: () => set((state) => ({ theme: state.theme === "light" ? "dark" : "light", })), }), { name: "app-storage" } ) ) ); ``` #### Granular Selectors (Prevent Re-renders) ```typescript // Bad: Subscribes to entire store, re-renders on any change const state = useAppStore(); // Good: Subscribes only to what's needed const user = useAppStore((state) => state.user); const theme = useAppStore((state) => state.theme); // Better: Export selector hooks export const useUser = () => useAppStore((state) => state.user); export const useTheme = () => useAppStore((state) => state.theme); ``` #### Slice Pattern (Scalable) ```typescript import { StateCreator } from "zustand"; export interface UserSlice { user: User | null; isAuthenticated: boolean; login: (credentials: Credentials) => Promise<void>; logout: () => void; } export const createUserSlice: StateCreator< UserSlice & UiSlice, [], [], UserSlice > = (set, get) => ({ user: null, isAuthenticated: false, login: async (credentials) => { const user = await authApi.login(credentials); set({ user, isAuthenticated: true }); }, logout: () => set({ user: null, isAuthenticated: false }), }); ``` ### Combining Client + Local-First State Use Zustand for UI state (sidebar, modals, preferences) and Dexie live queries for persistent local data (coops, tabs, drafts). Never duplicate Dexie data into Zustand -- let Dexie's reactive queries manage data. ### Advanced Zustand Patterns #### Persist Middleware Coop stores use `persist` for state survival across sessions: ```typescript import { create } from "zustand"; import { persist } from "zustand/middleware"; export const useUIStore = create<UIState>()( persist( (set) => ({ debugMode: false, isOfflineBannerVisible: false, toggleDebug: () => set((s) => ({ debugMode: !s.debugMode })), }), { name: "coop:debug-mode", // localStorage key partialize: (state) => ({ debugMode: state.debugMode }), // Only persist specific fields } ) ); ``` **`partialize` is critical** -- without it, transient UI state (modals, banners) gets persisted and restored on refresh, causing stale UI. #### Store Testing ```typescript beforeEach(() => { act(() => useCreateCoopStore.getState().reset()); // Reset between tests }); test("advances step when valid", () => { act(() => { useCreateCoopStore.getState().setField("name", "My Coop"); useCreateCoopStore.getState().nextStep(); }); expect(useCreateCoopStore.getState().currentStep).toBe(1); }); ``` #### When to Use Which Middleware | Middleware | Use When | |-----------|----------| | `persist` | State must survive page refresh (drafts, preferences) | | `devtools` | Development debugging (Redux DevTools integration) | | `immer` | Deep nested state updates (avoid in Coop -- prefer flat state) | ### Hook Boundary Rule **All state hooks MUST be in `@coop/shared`:** ```typescript // Correct: Import from shared import { useAuth, useCoops, useUIStore } from "@coop/shared"; // Wrong: Define hooks in app/extension export function useLocalState() { /* ... */ } ``` --- ## Part 2: Component Composition ### Core Principle > **Composition over configuration** -- Instead of adding props to customize behavior, compose smaller specialized components. ### Avoid Boolean Prop Explosion ```typescript // Bad: Boolean prop explosion <Button primary large withIcon loading disabled outlined /> // Good: Composition + explicit variants <Button variant="primary" size="lg"> <Button.Icon name="save" /> <Button.Text>Save</Button.Text> </Button> ``` ### Compound Components ```typescript // Good: Compound component pattern <Card> <Card.Header> <Card.Title>Coop Details</Card.Title> </Card.Header> <Card.Content> {/* Content */} </Card.Content> <Card.Footer> <Card.Actions> <Button>Edit</Button> </Card.Actions> </Card.Footer> </Card> ``` ### State Encapsulation in Providers ```typescript // Bad: State leaked to consumers function ParentComponent() { const [items, setItems] = useState([]); const [filter, setFilter] = useState(""); return <ItemList items={items} setItems={setItems} filter={filter} />; } // Good: State encapsulated in provider function ItemListProvider({ children }) { const [items, setItems] = useState([]); const [filter, setFilter] = useState(""); const filteredItems = useMemo( () => items.filter(i => i.name.includes(filter)), [items, filter] ); return ( <ItemListContext value={{ items: filteredItems, setFilter }}> {children} </ItemListContext> ); } ``` ### Explicit Variants Over Boolean Modes ```typescript // Bad: Boolean mode <Input error={true} /> <Input success={true} /> // Good: Explicit variant <Input variant="error" /> <Input variant="success" /> ``` ### React 19 Patterns Coop runs **React 19**. Use these new APIs: #### `use()` for Context and Promises ```typescript import { Suspense, use, useMemo } from "react"; // Replace useContext const value = use(MyContext); // Read a promise (suspends until resolved) function CoopDetails({ coopPromise }: { coopPromise: Promise<Coop> }) { const coop = use(coopPromise); // Suspends component return <h1>{coop.name}</h1>; } // Create the promise once per id, outside JSX function CoopDetailsContainer({ id }: { id: string }) { const coopPromise = useMemo(() => fetchCoop(id), [id]); return ( <Suspense fallback={<Skeleton />}> <CoopDetails coopPromise={coopPromise} /> </Suspense> ); } ``` #### `useOptimistic()` for Instant UI Feedback ```typescript import { useOptimistic } from "react"; function TabList({ tabs }: { tabs: Tab[] }) { const [optimisticTabs, addOptimistic] = useOptimistic( tabs, (current, newTab: Tab) => [...current, newTab] ); const handleAdd = async (tab: Tab) => { addOptimistic({ ...tab, status: "pending" }); await saveTab(tab); }; return optimisticTabs.map((t) => <TabCard key={t.id} tab={t} />); } ``` #### When to Use React 19 vs Existing Patterns | Task | React 19 | Existing | |------|----------|----------| | Read context | `use(Context)` | `useContext(Context)` -- both work | | Form submission | `useActionState` | React Hook Form + Zod (preferred for validation) | | Optimistic UI | `useOptimistic` | Manual state + rollback | | Async data | `use(promise)` + Suspense | Dexie live queries (preferred for local-first) | | Ref cleanup | Return cleanup from ref callback | `useEffect` cleanup | **Note:** For Coop, Dexie live queries remain the primary data source (local-first, reactive). Use `use()` for simple one-shot reads where Dexie is overkill. --- ## Part 3: React Compiler & Memoization > For full React Compiler integration details, see [compiler.md](./compiler.md) Coop uses `babel-plugin-react-compiler` in Vite configs. The compiler **automatically memoizes** components, hooks, and expressions -- eliminating most manual `useMemo`, `useCallback`, and `React.memo` usage. **Key rules:** - Don't add `useMemo`, `useCallback`, or `React.memo` by default -- the compiler handles it - Manual memoization still needed for: values passed to non-React APIs, shared hooks consumed across apps - Hooks in `@coop/shared` may still benefit from manual memoization (compiler optimizes per-app build) - When in doubt, profile first with React DevTools Profiler --- ## Part 4: Performance Optimization > For detailed performance patterns and bundle budgets, see [performance.md](./performance.md) **Critical rules:** - Eliminate request waterfalls -- use `Promise.all` for independent fetches - Use direct imports from large libraries (e.g., `import format from "date-fns/format"`) - Dynamic import for below-fold content (`lazy(() => import(...))`) - Use granular Zustand selectors, never subscribe to entire store - Virtualize long lists (50+ items) with @tanstack/react-virtual - CSS animations only (`transform`, `opacity`) **Bundle Budgets:** Main < 150KB, per-route < 50KB, total JS < 400KB (gzipped) --- ## Quick Reference **Before committing:** No serial awaits (use `Promise.all`), dynamic imports for below-fold, direct imports from large libs, long lists virtualized (50+ items), CSS-only animations. **Before designing a component:** Compose from smaller pieces, use explicit variants (not boolean props), use providers to avoid prop drilling, follow existing Coop patterns. **State management:** Zustand for UI state, Dexie for local-first data, granular selectors only, all hooks in `@coop/shared`, forms via React Hook Form + Zod. ## Anti-Patterns - Creating hooks in `app` or `extension` instead of `shared` - Selecting full Zustand state (`state => state`) in UI components - Introducing waterfalls when data can be fetched in parallel - Using deep imports from `@coop/shared/*` instead of barrel imports - Memoizing trivial values without measured performance impact ## Sub-Files - **[compiler.md](./compiler.md)** -- React Compiler integration, automatic memoization, when manual memo is still needed - **[performance.md](./performance.md)** -- Waterfall elimination, bundle budgets, re-render optimization, rendering performance
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub