+ );
+};
+```
+
+**Key rules:**
+
+- ✅ Use unique IDs from data (address, id, uuid)
+- ✅ Keys must be stable across re-renders
+- ⚠️ Only use index if list never reorders and items don't have IDs
+- ❌ Never use random values or `Math.random()`
+
+### ❌ Anti-Pattern: Using Index as Key for Dynamic Lists
+
+Using array index as key breaks React's reconciliation when lists can be reordered, filtered, or items added/removed.
+
+```typescript
+❌ WRONG: Index keys break reconciliation
+const TokenList = ({ tokens }: TokenListProps) => {
+ // If tokens can be reordered/filtered, this breaks React's reconciliation
+ return (
+
+ );
+};
+```
+
+**Problems with index keys:**
+
+- React can't track items when list reorders
+- State gets mixed up between items
+- Causes bugs with form inputs, focus, animations
+- Performance issues from unnecessary re-renders
+
+**Solution:** Always use unique, stable identifiers from your data (address, id, uuid). Only use index if the list is static and never reorders.
+
+### Virtualize Long Lists
+
+For lists with 100+ items, use virtualization to only render visible items.
+
+```typescript
+❌ WRONG: Rendering 1000+ items at once
+const TransactionList = ({ transactions }: TransactionListProps) => {
+ return (
+
;
+};
+```
+
+### ❌ Anti-Pattern: JSON.stringify in useEffect Dependencies
+
+Using `JSON.stringify` in dependencies is expensive and defeats memoization. However, there are valid use cases where you need to trigger effects when nested object properties change (deep equality) but not when only the object reference changes.
+
+**The Problem:**
+
+- `JSON.stringify` executes on every render, even if object hasn't changed
+- String comparison is slower than reference comparison
+- Creates new string objects, defeating memoization benefits
+- Can cause infinite loops if stringified value changes reference
+- Doesn't handle circular references or functions
+
+```typescript
+❌ WRONG: JSON.stringify runs on every render
+const usePolling = (input: PollingInput) => {
+ useEffect(() => {
+ startPolling(input);
+ }, [input && JSON.stringify(input)]); // Expensive! Runs every render
+};
+
+// ❌ WRONG: useMemo with JSON.stringify defeats purpose
+const jsonAccounts = useMemo(() => JSON.stringify(accounts), [accounts]);
+```
+
+**When You Need Deep Equality:**
+
+You want to trigger effects when:
+
+- ✅ Nested properties of an object change (deep equality)
+- ✅ Array elements change (deep equality)
+- ❌ Object reference changes but values are the same (should NOT trigger)
+
+**Best Practices for Deep Equality Dependencies:**
+
+```typescript
+✅ CORRECT: Option 1 - Use useEqualityCheck hook (Recommended)
+import { useEqualityCheck } from './hooks/useEqualityCheck';
+import { isEqual } from 'lodash';
+
+// useEqualityCheck returns a stable reference that only changes on deep equality
+const usePolling = (input: PollingInput) => {
+ // Returns same reference if deep values are equal, new reference if different
+ const stableInput = useEqualityCheck(input, isEqual);
+
+ useEffect(() => {
+ startPolling(stableInput);
+ }, [stableInput]); // Only triggers when deep values actually change
+};
+
+// Example: Network configuration object
+const TokenBalance = ({ networkConfig }: TokenBalanceProps) => {
+ // networkConfig may get new reference on every render, but values rarely change
+ const stableConfig = useEqualityCheck(networkConfig, isEqual);
+
+ useEffect(() => {
+ fetchBalance(stableConfig);
+ }, [stableConfig]); // Only re-fetches when config values actually change
+};
+```
+
+```typescript
+✅ CORRECT: Option 2 - useRef with deep equality check in effect
+import { isEqual } from 'lodash';
+
+const usePolling = (input: PollingInput) => {
+ const inputRef = useRef(input);
+
+ useEffect(() => {
+ // Only execute if deep values changed
+ if (!isEqual(input, inputRef.current)) {
+ inputRef.current = input;
+ startPolling(input);
+ }
+ }, [input]); // Effect runs on every input change, but logic only executes on deep equality
+};
+```
+
+```typescript
+✅ CORRECT: Option 3 - Custom hook for deep equality dependencies
+import { useEffect, useRef } from 'react';
+import { isEqual } from 'lodash';
+
+/**
+ * Returns a stable reference that only changes when deep equality check fails.
+ * Useful for useEffect dependencies that should trigger on nested changes.
+ */
+function useDeepEqualMemo(value: T, equalityFn: (a: T, b: T) => boolean = isEqual): T {
+ const ref = useRef<{ value: T; stable: T }>({ value, stable: value });
+
+ if (!equalityFn(value, ref.current.value)) {
+ ref.current = { value, stable: value };
+ }
+
+ return ref.current.stable;
+}
+
+// Usage:
+const usePolling = (input: PollingInput) => {
+ const stableInput = useDeepEqualMemo(input);
+
+ useEffect(() => {
+ startPolling(stableInput);
+ }, [stableInput]); // Stable reference, only changes on deep equality
+};
+```
+
+```typescript
+✅ CORRECT: Option 4 - Normalize to stable primitives (Best for Redux)
+// If possible, extract stable primitive values instead of objects
+const usePolling = (input: PollingInput) => {
+ // Extract only the values that actually matter
+ const inputId = useMemo(() => input.id, [input.id]);
+ const inputChainId = useMemo(() => input.chainId, [input.chainId]);
+ const inputRpcUrl = useMemo(() => input.rpcUrl, [input.rpcUrl]);
+
+ useEffect(() => {
+ startPolling(input);
+ }, [inputId, inputChainId, inputRpcUrl]); // Only depends on primitives
+};
+
+// For Redux: Use selectors that return stable references
+const selectNetworkConfig = createDeepEqualSelector(
+ [(state) => state.network],
+ (network) => ({
+ chainId: network.chainId,
+ rpcUrl: network.rpcUrl,
+ })
+);
+
+const TokenBalance = () => {
+ const networkConfig = useSelector(selectNetworkConfig); // Stable reference
+ useEffect(() => {
+ fetchBalance(networkConfig);
+ }, [networkConfig]); // Only triggers when config values change
+};
+```
+
+**When to Use Each Approach:**
+
+| Approach | Use When | Pros | Cons |
+| --------------------------- | ------------------------------------------- | ------------------------------------ | --------------------- |
+| **useEqualityCheck** | Objects/arrays from props or external state | Simple, reusable, handles edge cases | Requires hook import |
+| **useRef + isEqual** | One-off cases, custom logic needed | Full control, no extra hook | More boilerplate |
+| **Custom useDeepEqualMemo** | Need memoization behavior | Works like useMemo but deep equality | Custom implementation |
+| **Normalize to primitives** | Can extract stable IDs/values | Most performant, clear dependencies | Not always possible |
+
+**Key Principles:**
+
+1. ✅ **Use deep equality when object references change frequently but values don't** - Common with Redux selectors, props from parent components, or API responses
+2. ✅ **Prefer `useEqualityCheck` hook** - Already implemented in codebase, handles edge cases
+3. ✅ **Normalize when possible** - Extract stable primitives (IDs, strings, numbers) instead of objects
+4. ❌ **Never use `JSON.stringify`** - Expensive, unreliable, breaks with functions/circular refs
+5. ❌ **Don't skip dependencies** - Always include dependencies, use deep equality to stabilize them
+
+### ❌ Anti-Pattern: useEffect with Incomplete Dependencies (Stale Closures)
+
+Empty dependency arrays that use values from closure create stale closures.
+
+```typescript
+❌ WRONG: Empty deps but uses values from closure
+const Name = ({ type, name }: NameProps) => {
+ useEffect(() => {
+ trackEvent({
+ properties: {
+ petname_category: type, // Uses 'type' from closure
+ has_petname: Boolean(name?.length), // Uses 'name' from closure
+ },
+ });
+ }, []); // Empty deps - 'type' and 'name' are stale!
+};
+```
+
+**Problems:**
+
+- Values captured in closure may be stale (initial values, not current)
+- Effect runs once but uses outdated values
+- Can lead to incorrect analytics/metrics
+- Hard to debug because values appear correct in code
+
+```typescript
+✅ CORRECT: Include all dependencies
+const Name = ({ type, name }: NameProps) => {
+ useEffect(() => {
+ trackEvent({
+ properties: {
+ petname_category: type,
+ has_petname: Boolean(name?.length),
+ },
+ });
+ }, [type, name]); // Include all dependencies
+
+ // OR if you truly only want to track once:
+ const hasTrackedRef = useRef(false);
+ useEffect(() => {
+ if (!hasTrackedRef.current) {
+ trackEvent({
+ properties: {
+ petname_category: type,
+ has_petname: Boolean(name?.length),
+ },
+ });
+ hasTrackedRef.current = true;
+ }
+ }, [type, name]);
+};
+```
+
+### ❌ Anti-Pattern: Wrong Dependencies in useMemo/useCallback
+
+Missing dependencies in `useMemo`/`useCallback` cause stale closures and bugs.
+
+```typescript
+❌ WRONG: Missing dependencies
+const TokenList = ({ tokens, filter }: TokenListProps) => {
+ // Dependencies are wrong - should include filter!
+ const filteredTokens = useMemo(() => {
+ return tokens.filter(token => token.symbol.includes(filter));
+ }, [tokens]); // Missing filter dependency!
+
+ return
...
;
+};
+```
+
+**Problems:**
+
+- Stale closures capture old values
+- Effects/calculations use outdated data
+- Hard to debug because code looks correct
+- Can cause incorrect behavior or infinite loops
+
+**Solution:** Always include all dependencies. Use ESLint rule `react-hooks/exhaustive-deps` to catch missing dependencies automatically.
+
+```typescript
+✅ CORRECT: Include all dependencies
+const TokenList = ({ tokens, filter }: TokenListProps) => {
+ const filteredTokens = useMemo(() => {
+ return tokens.filter(token => token.symbol.includes(filter));
+ }, [tokens, filter]); // All dependencies included
+
+ return
...
;
+};
+```
+
+### ❌ Anti-Pattern: Cascading useEffect Chains
+
+Multiple effects where one sets state that triggers another cause unnecessary re-renders.
+
+```typescript
+❌ WRONG: Effect chain - first effect sets state, second responds to it
+const useHistoricalPrices = () => {
+ const [prices, setPrices] = useState([]);
+ const [metadata, setMetadata] = useState(null);
+
+ // First effect fetches and updates Redux
+ useEffect(() => {
+ fetchPrices();
+ const intervalId = setInterval(fetchPrices, 60000);
+ return () => clearInterval(intervalId);
+ }, [chainId, address]);
+
+ // Second effect responds to Redux state change
+ useEffect(() => {
+ const pricesToSet = historicalPricesNonEvm?.[address]?.intervals ?? [];
+ setPrices(pricesToSet); // Triggers third effect
+ }, [historicalPricesNonEvm, address]);
+
+ // Third effect depends on state from second effect
+ useEffect(() => {
+ const metadataToSet = deriveMetadata(prices);
+ setMetadata(metadataToSet);
+ }, [prices]);
+};
+```
+
+**Problems:**
+
+- Multiple re-renders from cascading effects
+- Hard to reason about execution order
+- Can cause race conditions
+- Performance overhead from multiple effect runs
+
+```typescript
+✅ CORRECT: Combine effects or compute during render
+const useHistoricalPrices = () => {
+ // Compute prices during render from Redux state
+ const prices = useMemo(() => {
+ return historicalPricesNonEvm?.[address]?.intervals ?? [];
+ }, [historicalPricesNonEvm, address]);
+
+ // Compute metadata during render from prices
+ const metadata = useMemo(() => {
+ return deriveMetadata(prices);
+ }, [prices]);
+
+ // Single effect for async operations
+ useEffect(() => {
+ fetchPrices();
+ const intervalId = setInterval(fetchPrices, 60000);
+ return () => clearInterval(intervalId);
+ }, [chainId, address]);
+
+ return { prices, metadata };
+};
+```
+
+### ❌ Anti-Pattern: Conditional Early Return with All Dependencies
+
+Effects with conditional early returns that still include all dependencies in the array.
+
+```typescript
+❌ WRONG: Early return but dependencies include unused values
+const useHistoricalPrices = ({ isEvm, chainId, address }: Props) => {
+ useEffect(() => {
+ if (isEvm) {
+ return; // Early return
+ }
+ // Only uses chainId and address when not EVM
+ fetchPrices(chainId, address);
+ }, [isEvm, chainId, address]); // Includes all deps even when unused
+};
+```
+
+**Problems:**
+
+- Effect dependencies include values not used when condition is true
+- Can cause unnecessary effect re-runs
+- Unclear intent
+- ESLint may warn about missing dependencies
+
+```typescript
+✅ CORRECT: Split into separate effects or use proper conditional logic
+// Option 1: Split effects
+const useHistoricalPrices = ({ isEvm, chainId, address }: Props) => {
+ useEffect(() => {
+ if (!isEvm) {
+ fetchPrices(chainId, address);
+ }
+ }, [isEvm, chainId, address]); // All deps are used
+
+ // OR Option 2: Separate effects
+ useEffect(() => {
+ if (isEvm) return;
+ fetchPrices(chainId, address);
+ }, [isEvm]); // Only depends on condition
+
+ useEffect(() => {
+ if (!isEvm) {
+ fetchPrices(chainId, address);
+ }
+ }, [chainId, address]); // Only when not EVM
+};
+```
+
+---
+
+### ❌ Anti-Pattern: Regular Variables Instead of useRef
+
+Using regular variables instead of `useRef` for values that need to persist across renders.
+
+```typescript
+❌ WRONG: Regular variable gets reset on every render
+const usePolling = (input: PollingInput) => {
+ let isMounted = false; // Gets reset every render!
+
+ useEffect(() => {
+ isMounted = true;
+ startPolling(input);
+
+ return () => {
+ isMounted = false;
+ };
+ }, [input]);
+};
+```
+
+**Problems:**
+
+- Regular variable gets reset on every render
+- Doesn't persist across renders
+- Closure captures stale value
+- Can cause bugs with async operations
+
+```typescript
+✅ CORRECT: Use useRef for persistent values
+const usePolling = (input: PollingInput) => {
+ const isMountedRef = useRef(false);
+
+ useEffect(() => {
+ isMountedRef.current = true;
+ startPolling(input);
+
+ return () => {
+ isMountedRef.current = false;
+ };
+ }, [input]);
+};
+```
+
+## Non-Deterministic Hook Execution
+
+### ❌ Anti-Pattern: Conditional Hook Calls
+
+Hooks must be called in the same order on every render. Conditional hooks cause bugs and performance issues.
+
+```typescript
+❌ WRONG: Conditional hook execution
+const TokenDisplay = ({ token, showDetails }: TokenDisplayProps) => {
+ const [balance, setBalance] = useState('0');
+
+ if (showDetails) {
+ // ⚠️ Hook called conditionally - breaks Rules of Hooks!
+ const [metadata, setMetadata] = useState(null);
+ useEffect(() => {
+ fetchMetadata(token.id).then(setMetadata);
+ }, [token.id]);
+ }
+
+ return
+ );
+}
+```
+
+React Compiler determines that `` and `` can be reused even as `friends` or `onlineCount` change, avoiding unnecessary re-renders.
+
+#### Example: Automatic Memoization of Expensive Calculations
+
+```typescript
+// React Compiler automatically memoizes expensive computations
+function TableContainer({ items }) {
+ // This expensive calculation is automatically memoized
+ const data = expensivelyProcessAReallyLargeArrayOfObjects(items);
+ return
;
+}
+```
+
+**Note:** For truly expensive functions used across multiple components, consider implementing memoization outside React, as React Compiler only memoizes within components/hooks and doesn't share memoization across components.
+
+### React Compiler Assumptions
+
+React Compiler assumes your code:
+
+- ✅ Is valid, semantic JavaScript
+- ✅ Tests nullable/optional values before accessing (e.g., enable `strictNullChecks` in TypeScript)
+- ✅ Follows the [Rules of React](https://react.dev/reference/rules)
+
+React Compiler can verify many Rules of React statically and will **skip compilation** when it detects errors. Install [eslint-plugin-react-compiler](https://www.npmjs.com/package/eslint-plugin-react-compiler) to see compilation errors.
+
+### React Compiler Limitations
+
+#### Single-File Compilation
+
+**React Compiler operates on a single file at a time** - it only uses information within that file to perform optimizations. This means:
+
+- ✅ Works well for most React code (React's programming model uses plain JavaScript values)
+- ❌ Cannot see across file boundaries
+- ❌ Cannot use TypeScript/Flow type information (has its own internal type system)
+- ❌ Cannot optimize based on information from other files
+
+**Impact:** Code that depends on values from other files may not be optimized as effectively.
+
+#### Effects and Dependency Memoization (Open Research Area)
+
+**Effects memoization is still an open area of research.** React Compiler can sometimes memoize differently from manual memoization, which can cause issues with effects that rely on dependencies not changing to prevent infinite loops.
+
+**Recommendation:**
+
+- ✅ **Keep existing `useMemo()` and `useCallback()` calls** - Especially for effect dependencies to ensure behavior doesn't change
+- ✅ **Write new code without `useMemo`/`useCallback`** - Let React Compiler handle it automatically
+- ⚠️ React Compiler will statically validate that auto-memoization matches existing manual memoization
+- ⚠️ If it can't prove they're the same, the component/hook is safely skipped over
+
+```typescript
+✅ CORRECT: Keep existing useMemo for effect dependencies
+const TokenBalance = ({ address }: Props) => {
+ const network = useSelector(getNetwork);
+
+ // Keep useMemo to ensure effect behavior is preserved
+ const networkConfig = useMemo(() => ({
+ chainId: network.chainId,
+ rpcUrl: network.rpcUrl,
+ }), [network.chainId, network.rpcUrl]);
+
+ useEffect(() => {
+ fetchBalance(address, networkConfig);
+ }, [address, networkConfig]); // Stable reference prevents infinite loops
+};
+
+✅ CORRECT: New code - no manual memoization needed
+const TokenList = ({ tokens }: TokenListProps) => {
+ // React Compiler handles this automatically
+ const sortedTokens = tokens
+ .slice()
+ .sort((a, b) => parseFloat(b.balance) - parseFloat(a.balance));
+
+ return (
+
+ {sortedTokens.map(token => (
+
+ ))}
+
+ );
+};
+```
+
+### When Manual Memoization is Still Required
+
+Due to React Compiler's **single-file compilation** limitation and inability to see across file boundaries, manual memoization is required for:
+
+#### 1. Cross-File Dependencies
+
+React Compiler operates on single files, so it cannot optimize computations that depend on values from other files.
+
+```typescript
+❌ WRONG: React Compiler can't see across files
+// file1.ts
+export const getProcessedTokens = (tokens: Token[]) => {
+ return tokens.map(/* expensive processing */);
+};
+
+// file2.tsx
+import { getProcessedTokens } from './file1';
+
+const AssetList = () => {
+ const tokens = useSelector(getTokens);
+
+ // React Compiler can't see into getProcessedTokens from another file
+ const processed = getProcessedTokens(tokens); // Runs on every render!
+};
+
+✅ CORRECT: Manual memoization for cross-file dependencies
+const AssetList = () => {
+ const tokens = useSelector(getTokens);
+
+ // Manual memoization required - function from another file
+ const processed = useMemo(
+ () => getProcessedTokens(tokens),
+ [tokens]
+ );
+};
+```
+
+**Why:** React Compiler only sees code within the current file. Functions imported from other files are opaque.
+
+#### 2. Redux Selectors and External State Management
+
+React Compiler cannot optimize values from Redux selectors or other external state management libraries.
+
+```typescript
+❌ WRONG: React Compiler can't optimize Redux selectors
+const AssetList = () => {
+ const tokens = useSelector(getTokens); // External state
+ const balances = useSelector(getBalances); // External state
+
+ // React Compiler can't see into Redux - manual memoization needed
+ const tokensWithBalances = tokens.map(token => ({
+ ...token,
+ balance: balances[token.address],
+ }));
+};
+
+✅ CORRECT: Manual memoization required for Redux-derived values
+const AssetList = () => {
+ const tokens = useSelector(getTokens);
+ const balances = useSelector(getBalances);
+
+ // Manual memoization required - React Compiler can't optimize Redux values
+ const tokensWithBalances = useMemo(
+ () => tokens.map(token => ({
+ ...token,
+ balance: balances[token.address],
+ })),
+ [tokens, balances] // Dependencies from external state
+ );
+};
+```
+
+**Why:** Redux selectors return values from outside React's compilation scope. React Compiler operates on single files and can't track changes to external state.
+
+#### 3. Values from External Hooks or Libraries
+
+React Compiler cannot optimize values returned from hooks in external libraries or custom hooks defined in other files.
+
+```typescript
+❌ WRONG: React Compiler can't optimize external hook values
+// hooks.ts (different file)
+export function useTokenTracker({ tokens }) {
+ // Complex logic using Redux, context, etc.
+ return { tokensWithBalances: /* ... */ };
+}
+
+// component.tsx
+import { useTokenTracker } from './hooks';
+
+const TokenTracker = ({ tokens }: Props) => {
+ const { tokensWithBalances } = useTokenTracker({ tokens }); // External hook
+
+ // React Compiler can't see into useTokenTracker from another file
+ const formattedTokens = tokensWithBalances.map(token => ({
+ ...token,
+ formattedBalance: formatCurrency(token.balance),
+ }));
+};
+
+✅ CORRECT: Manual memoization for external hook values
+const TokenTracker = ({ tokens }: Props) => {
+ const { tokensWithBalances } = useTokenTracker({ tokens });
+
+ // Manual memoization required - hook from another file
+ const formattedTokens = useMemo(
+ () => tokensWithBalances.map(token => ({
+ ...token,
+ formattedBalance: formatCurrency(token.balance),
+ })),
+ [tokensWithBalances, formatCurrency]
+ );
+};
+```
+
+**Why:** React Compiler operates on single files. Hooks defined in other files are opaque, especially if they use Redux, context, or other external state.
+
+#### 4. Conditional Logic with External State
+
+When conditional logic combines props/state with external state (Redux, context from other files), React Compiler may not optimize effectively.
+
+```typescript
+❌ WRONG: Conditional logic with external state may not be optimized
+const AssetPicker = ({ hideZeroBalance }: Props) => {
+ const tokens = useSelector(getTokens); // External state
+ const balances = useSelector(getBalances); // External state
+
+ // Conditional filtering - React Compiler may not optimize this
+ const filteredTokens = hideZeroBalance
+ ? tokens.filter(t => balances[t.address] > 0)
+ : tokens;
+};
+
+✅ CORRECT: Manual memoization for conditional logic with external dependencies
+const AssetPicker = ({ hideZeroBalance }: Props) => {
+ const tokens = useSelector(getTokens);
+ const balances = useSelector(getBalances);
+
+ // Manual memoization required - conditional + external state
+ const filteredTokens = useMemo(
+ () => hideZeroBalance
+ ? tokens.filter(t => balances[t.address] > 0)
+ : tokens,
+ [hideZeroBalance, tokens, balances]
+ );
+};
+```
+
+**Why:** React Compiler can optimize simple conditionals based on props/state within the same file, but struggles when combined with external state from other files.
+
+#### 5. Functions Passed to Third-Party Components
+
+When passing functions to components from external libraries (node_modules) or components in other files, React Compiler cannot optimize.
+
+```typescript
+❌ WRONG: React Compiler can't optimize callbacks for external components
+import { ThirdPartyList } from 'some-library'; // External library
+
+const TokenList = ({ tokens, onSelect }: Props) => {
+ const dispatch = useDispatch();
+
+ // React Compiler can't see into third-party component from node_modules
+ return (
+ {
+ dispatch(selectToken(token));
+ onSelect(token);
+ }}
+ />
+ );
+};
+
+✅ CORRECT: Manual useCallback for external component callbacks
+const TokenList = ({ tokens, onSelect }: Props) => {
+ const dispatch = useDispatch();
+
+ // Manual useCallback required - external component from another file/library
+ const handleItemClick = useCallback(
+ (token: Token) => {
+ dispatch(selectToken(token));
+ onSelect(token);
+ },
+ [dispatch, onSelect]
+ );
+
+ return (
+
+ );
+};
+```
+
+**Why:** React Compiler operates on single files. Components from `node_modules` or other files are opaque and cannot be analyzed.
+
+#### 6. Computations Dependent on Refs or DOM Values
+
+When computations depend on `useRef` values, DOM queries, or other mutable values, React Compiler cannot track changes statically.
+
+```typescript
+❌ WRONG: React Compiler can't optimize ref-based computations
+const TokenInput = ({ tokens }: Props) => {
+ const inputRef = useRef(null);
+ const [filter, setFilter] = useState('');
+
+ // React Compiler can't track ref.current changes statically
+ const filteredTokens = tokens.filter(token => {
+ const inputValue = inputRef.current?.value || filter;
+ return token.symbol.includes(inputValue);
+ });
+};
+
+✅ CORRECT: Manual memoization for ref-dependent computations
+const TokenInput = ({ tokens }: Props) => {
+ const inputRef = useRef(null);
+ const [filter, setFilter] = useState('');
+
+ // Manual memoization required - refs are mutable
+ const filteredTokens = useMemo(() => {
+ const inputValue = inputRef.current?.value || filter;
+ return tokens.filter(token => token.symbol.includes(inputValue));
+ }, [tokens, filter]); // Note: ref.current not in deps (intentional)
+};
+```
+
+**Why:** Refs are mutable values that React Compiler cannot track statically. DOM queries and other runtime values also cannot be analyzed at compile time.
+
+#### 7. Reselect Selectors and Complex Compositions
+
+When using Reselect selectors defined in other files, React Compiler cannot optimize the selector itself.
+
+```typescript
+❌ WRONG: React Compiler can't optimize Reselect selectors from other files
+// selectors.ts (different file)
+export const selectTotalBalance = createSelector(
+ [getAccounts, getBalances],
+ (accounts, balances) => accounts.reduce((sum, acc) =>
+ sum + balances[acc.address], 0
+ )
+);
+
+// component.tsx
+import { selectTotalBalance } from './selectors';
+
+const Dashboard = () => {
+ const accounts = useSelector(getAccounts);
+ const balances = useSelector(getBalances);
+
+ // React Compiler can't see into selectTotalBalance from another file
+ const totalBalance = accounts.reduce((sum, acc) =>
+ sum + balances[acc.address], 0
+ );
+};
+
+✅ CORRECT: Use Reselect selector (already memoized) or manual memoization
+// Option 1: Use Reselect selector (preferred - already memoized)
+const Dashboard = () => {
+ const totalBalance = useSelector(selectTotalBalance); // Reselect handles memoization
+};
+
+// Option 2: Manual memoization if selector not available
+const Dashboard = () => {
+ const accounts = useSelector(getAccounts);
+ const balances = useSelector(getBalances);
+
+ const totalBalance = useMemo(
+ () => accounts.reduce((sum, acc) => sum + balances[acc.address], 0),
+ [accounts, balances]
+ );
+};
+```
+
+**Why:** Reselect selectors defined in other files are opaque to React Compiler. However, Reselect already provides memoization, so this is often not an issue.
+
+#### 8. Effect Dependencies (Keep Existing Memoization)
+
+**Effects memoization is still an open area of research.** React Compiler may memoize differently than manual memoization, which can break effects that rely on stable dependencies.
+
+```typescript
+⚠️ IMPORTANT: Keep existing useMemo/useCallback for effect dependencies
+const TokenBalance = ({ address }: Props) => {
+ const [balance, setBalance] = useState('0');
+ const network = useSelector(getNetwork);
+
+ // Keep useMemo to ensure effect behavior is preserved
+ const networkConfig = useMemo(() => ({
+ chainId: network.chainId,
+ rpcUrl: network.rpcUrl,
+ }), [network.chainId, network.rpcUrl]);
+
+ useEffect(() => {
+ // Stable networkConfig reference prevents infinite loops
+ fetchBalance(address, networkConfig);
+ }, [address, networkConfig]);
+};
+```
+
+**Why:** React Compiler will statically validate that auto-memoization matches existing manual memoization. If it can't prove they're the same, it safely skips compilation. To ensure effect behavior doesn't change, **keep existing `useMemo`/`useCallback` calls for effect dependencies**.
+
+**Recommendation:**
+
+- ✅ Keep existing `useMemo`/`useCallback` for effects
+- ✅ Write new code without manual memoization (let React Compiler handle it)
+- ⚠️ If you notice unexpected effect behavior, [file an issue](https://github.com/facebook/react/issues)
+
+#### 9. Context Values from External Providers
+
+When consuming context from providers defined in other files, React Compiler may not optimize effectively.
+
+```typescript
+❌ WRONG: React Compiler may not optimize context from other files
+// context.tsx (different file)
+export const ExternalI18nContext = createContext(/* ... */);
+
+// component.tsx
+import { ExternalI18nContext } from './context';
+
+const TokenDisplay = ({ token }: Props) => {
+ const { formatCurrency, locale } = useContext(ExternalI18nContext);
+
+ // React Compiler may not optimize context values from another file
+ const formattedBalance = formatCurrency(token.balance, locale);
+};
+
+✅ CORRECT: Manual memoization if needed
+const TokenDisplay = ({ token }: Props) => {
+ const { formatCurrency, locale } = useContext(ExternalI18nContext);
+
+ // Manual memoization if this computation is expensive
+ const formattedBalance = useMemo(
+ () => formatCurrency(token.balance, locale),
+ [formatCurrency, token.balance, locale]
+ );
+};
+```
+
+**Why:** Context providers defined in other files may not be fully analyzed by React Compiler. However, simple context consumption often works fine without manual memoization.
+
+#### 10. Computations with Multiple Cross-File Dependencies
+
+When computations combine multiple external sources from different files (Redux + Context + imported functions), React Compiler may not optimize effectively.
+
+```typescript
+❌ WRONG: Multiple cross-file dependencies - React Compiler may not optimize
+import { formatCurrency } from './utils'; // External function
+import { CurrencyContext } from './context'; // External context
+
+const AssetCard = ({ assetId }: Props) => {
+ const asset = useSelector(state => selectAsset(state, assetId)); // Redux
+ const { locale } = useContext(CurrencyContext); // Context from another file
+
+ // Multiple external dependencies from different files
+ const displayData = {
+ name: asset.name,
+ balance: formatCurrency(asset.balance, locale), // Function from another file
+ };
+};
+
+✅ CORRECT: Manual memoization for multiple cross-file dependencies
+const AssetCard = ({ assetId }: Props) => {
+ const asset = useSelector(state => selectAsset(state, assetId));
+ const { locale } = useContext(CurrencyContext);
+
+ // Manual memoization required - multiple external sources from different files
+ const displayData = useMemo(
+ () => ({
+ name: asset.name,
+ balance: formatCurrency(asset.balance, locale),
+ }),
+ [asset, locale] // formatCurrency is stable if from another file
+ );
+};
+```
+
+**Why:** React Compiler can optimize simple prop/state combinations within a single file, but struggles with complex dependency chains spanning multiple files.
+
+### Decision Tree: Do You Need Manual Memoization?
+
+```text
+Is the computation/value:
+├─ From another file (imported function/hook)? → ✅ Manual memoization required
+├─ From Redux selectors? → ✅ Manual memoization required
+├─ From external library (node_modules)? → ✅ Manual memoization required
+├─ Used as useEffect dependency? → ✅ Keep existing useMemo/useCallback
+├─ Depends on refs or DOM values? → ✅ Manual memoization required
+├─ Combines multiple cross-file dependencies? → ✅ Manual memoization required
+└─ Simple props/state within same file? → ❌ React Compiler handles it
+
+Is the callback:
+├─ Used as useEffect dependency? → ✅ Keep existing useCallback
+├─ Passed to external component/library? → ✅ Manual useCallback required
+├─ Depends on imported functions/hooks? → ✅ Manual useCallback required
+└─ Simple prop handler within file? → ❌ React Compiler handles it
+```
+
+### Summary: React Compiler Capabilities and Limitations
+
+**React Compiler CAN optimize:**
+
+1. ✅ Components and hooks within the same file
+2. ✅ Expensive calculations within components/hooks
+3. ✅ Fine-grained reactivity (preventing cascading re-renders)
+4. ✅ Inline objects/functions with React-controlled dependencies
+5. ✅ Derived state from props/state within the file
+6. ✅ Simple conditional memoization based on props/state
+
+**React Compiler CANNOT optimize:**
+
+1. ❌ Code across file boundaries (single-file compilation)
+2. ❌ Functions/hooks imported from other files
+3. ❌ Redux selectors and external state management
+4. ❌ Components from external libraries (node_modules)
+5. ❌ Computations dependent on refs or DOM values
+6. ❌ TypeScript/Flow type information (uses own type system)
+7. ⚠️ Effect dependencies (keep existing useMemo/useCallback - open research area)
+
+**Key Limitations:**
+
+- **Single-file compilation** - Cannot see across files
+- **No type information** - Doesn't use TypeScript/Flow types
+- **Effects memoization** - Still an open research area
+
+**Best Practices:**
+
+- ✅ Write new code without `useMemo`/`useCallback` - let React Compiler handle it
+- ✅ Keep existing `useMemo`/`useCallback` for effect dependencies
+- ✅ Use manual memoization for cross-file dependencies
+- ✅ Install [eslint-plugin-react-compiler](https://www.npmjs.com/package/eslint-plugin-react-compiler) to catch compilation errors
+
+**Rule of thumb:** If it's within the same file and uses props/state, React Compiler handles it. If it crosses file boundaries (imports, Redux, external libraries), use manual memoization.
+
+---