| 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
-
Predictability: State changes should be predictable and traceable.
-
Composition: Build complex from simple, not through inheritance.
-
Explicit Over Magic: Prefer verbose clarity over clever brevity.
-
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
function App() {
return (
<Layout
header={<Header user={user} onLogout={logout} />}
sidebar={<Sidebar items={items} selected={selected} onSelect={select} />}
content={<Content data={data} user={user} />}
/>
);
}
function App() {
return (
<Layout>
<Header>
<UserMenu user={user} onLogout={logout} />
</Header>
<Sidebar>
<Navigation items={items} selected={selected} onSelect={select} />
</>
);
}
() {
[activeIndex, setActiveIndex] = (defaultIndex);
(
);
}
. = () {
;
};
. = () {
{ activeIndex, setActiveIndex } = ();
(
);
};
. = () {
{ activeIndex } = ();
.(children)[activeIndex];
};
Custom Hooks for Logic Reuse
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];
}
function useAsync(asyncFunction, immediate = true) {
const [status, setStatus] = ();
[value, setValue] = ();
[error, setError] = ();
execute = ( () => {
();
();
();
{
response = ();
(response);
();
} (error) {
(error);
();
}
}, [asyncFunction]);
( {
(immediate) {
();
}
}, [execute, immediate]);
{ execute, status, value, error };
}
State Management Patterns
function App() {
const [searchQuery, setSearchQuery] = useState('');
const [results, setResults] = useState([]);
}
function SearchComponent() {
const [searchQuery, setSearchQuery] = useState('');
const [results, setResults] = useState([]);
}
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, : action. };
:
();
}
}
() {
[state, dispatch] = (reducer, {
: ,
: ,
:
});
= () => {
({ : });
{
data = api.();
({ : , : data });
} (error) {
({ : , : error. });
}
};
}
[isLoading, setIsLoading] = ();
[isError, setIsError] = ();
[isSuccess, setIsSuccess] = ();
[status, setStatus] = ();
Performance Patterns
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
const handleClick = useCallback((id) => {
setSelected(id);
}, []);
const MemoizedChild = React.memo(function Child({ data, onClick }) {
return <div onClick={onClick}>{data.name}</div>;
});
const value = useMemo(() => a + b, [a, b]);
Error Boundaries
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);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
<ErrorBoundary fallback={<ErrorPage />}>
< />
</>
Mental Model
Abramov approaches React code by asking:
- Where should this state live? As low as possible, as high as necessary
- Is this predictable? Can I trace how we got here?
- Can this be composed? Small pieces that combine well
- Is this testable? Pure functions, clear inputs/outputs
- 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