---
name: abramov-state-composition
description: Write JavaScript code in the style of Dan Abramov, co-creator of Redux and React core team member. Emphasizes predictable state management, composition over inheritance, and developer experience. Use when building React applications or managing complex state.
---
# Dan Abramov Style Guide
## Overview
Dan Abramov is the co-creator of Redux, Create React App, and a member of the React core team. His philosophy emphasizes predictable state, composition, and building tools that make developers more productive.
## Core Philosophy
> "Redux is not the answer to all state management. It's one tool in the toolbox."
> "The best code is the code that doesn't exist."
> "Make impossible states impossible."
Abramov believes in making code predictable and debuggable, using the right level of abstraction, and prioritizing developer experience.
## Design Principles
1. **Predictability**: State changes should be predictable and traceable.
2. **Composition**: Build complex from simple, not through inheritance.
3. **Explicit Over Magic**: Prefer verbose clarity over clever brevity.
4. **Developer Experience**: Tools should help developers, not fight them.
## When Writing Code
### Always
- Keep state as flat as possible
- Make state changes predictable and traceable
- Use composition to build complex components
- Colocate state with components that need it
- Write components that are easy to test
- Think about error boundaries
### Never
- Mutate state directly
- Put everything in global state
- Use inheritance for component reuse
- Create deeply nested state structures
- Ignore render performance in lists
- Swallow errors silently
### Prefer
- Local state over global when possible
- Hooks over class components
- Function composition over inheritance
- Explicit data flow over prop drilling solutions
- Pure functions for state updates
- Custom hooks for reusable logic
## Code Patterns
### Component Composition
```javascript
// BAD: Prop drilling and inheritance thinking
function App() {
return (
}
sidebar={}
content={}
/>
);
}
// GOOD: Composition with children
function App() {
return (
);
}
// Compound Components Pattern
function Tabs({ children, defaultIndex = 0 }) {
const [activeIndex, setActiveIndex] = useState(defaultIndex);
return (
{children}
);
}
Tabs.List = function TabList({ children }) {
return
{children}
;
};
Tabs.Tab = function Tab({ index, children }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
return (
);
};
Tabs.Panels = function TabPanels({ children }) {
const { activeIndex } = useContext(TabsContext);
return Children.toArray(children)[activeIndex];
};
// Usage - composable and flexible
FirstSecondFirst contentSecond content
```
### Custom Hooks for Logic Reuse
```javascript
// Extract reusable logic into custom hooks
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = useCallback((value) => {
try {
const valueToStore = value instanceof Function
? value(storedValue)
: value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
}, [key, storedValue]);
return [storedValue, setValue];
}
// Async data fetching hook
function useAsync(asyncFunction, immediate = true) {
const [status, setStatus] = useState('idle');
const [value, setValue] = useState(null);
const [error, setError] = useState(null);
const execute = useCallback(async () => {
setStatus('pending');
setValue(null);
setError(null);
try {
const response = await asyncFunction();
setValue(response);
setStatus('success');
} catch (error) {
setError(error);
setStatus('error');
}
}, [asyncFunction]);
useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);
return { execute, status, value, error };
}
```
### State Management Patterns
```javascript
// Pattern 1: Colocate state
// State should live as close to where it's used as possible
// BAD: Lifting state too high
function App() {
const [searchQuery, setSearchQuery] = useState('');
const [results, setResults] = useState([]);
// ... passed down through many layers
}
// GOOD: State lives where it's used
function SearchComponent() {
const [searchQuery, setSearchQuery] = useState('');
const [results, setResults] = useState([]);
// Only this component cares about search
}
// Pattern 2: Reducer for complex state
function reducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true, error: null };
case 'FETCH_SUCCESS':
return { ...state, loading: false, data: action.payload };
case 'FETCH_ERROR':
return { ...state, loading: false, error: action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
function DataComponent() {
const [state, dispatch] = useReducer(reducer, {
data: null,
loading: false,
error: null
});
// Actions are explicit and traceable
const fetchData = async () => {
dispatch({ type: 'FETCH_START' });
try {
const data = await api.getData();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', payload: error.message });
}
};
}
// Pattern 3: Make impossible states impossible
// BAD: Multiple booleans that can conflict
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
// What if isLoading AND isError are both true?
// GOOD: Single status that can only be one thing
const [status, setStatus] = useState('idle'); // 'idle' | 'loading' | 'error' | 'success'
```
### Performance Patterns
```javascript
// Memoize expensive computations
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// Memoize callbacks passed to children
const handleClick = useCallback((id) => {
setSelected(id);
}, []);
// Memoize components that receive stable props
const MemoizedChild = React.memo(function Child({ data, onClick }) {
return
{data.name}
;
});
// Don't over-optimize! Profile first
// BAD: Premature optimization everywhere
const value = useMemo(() => a + b, [a, b]); // Simple addition doesn't need memo
// GOOD: Optimize what matters
// - Large lists with React.memo on items
// - Expensive computations with useMemo
// - Context values to prevent cascading rerenders
```
### Error Boundaries
```javascript
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
// Log to error reporting service
}
render() {
if (this.state.hasError) {
return this.props.fallback ||
Something went wrong.
;
}
return this.props.children;
}
}
// Usage: wrap parts of your app
}>
```
## Mental Model
Abramov approaches React code by asking:
1. **Where should this state live?** As low as possible, as high as necessary
2. **Is this predictable?** Can I trace how we got here?
3. **Can this be composed?** Small pieces that combine well
4. **Is this testable?** Pure functions, clear inputs/outputs
5. **What can go wrong?** Error boundaries, loading states
## Signature Abramov Moves
- Composition over inheritance, always
- Custom hooks for reusable logic
- useReducer for complex state transitions
- Make impossible states impossible
- Colocate state near usage
- Memoize strategically, not everywhere