| name | frontend-framework-guide |
| description | Master modern frontend frameworks for building production-quality web applications. Use this skill for React patterns and best practices, hooks and state management, component architecture, performance optimization, testing strategies, and framework selection. Triggers include: React component patterns, custom hooks development, state management (Redux, Context, Zustand), component composition, performance with useCallback/useMemo, dependency arrays, testing React components, framework comparisons, and architectural decisions. Essential for frontend developers building scalable, maintainable, and performant React applications. |
Frontend Framework Guide
Patterns and best practices for building production applications with modern frameworks, primarily React.
React is the dominant frontend framework for building scalable applications. Mastering React's mental model—component composition, hooks, state management, and performance optimization—is essential for professional development.
Core Concept: Components & Composition
The React Mental Model
React is built on declarative component composition:
State → Component → UI
User interaction → State change → Re-render → New UI
Declarative: Describe what UI should look like, React handles updates.
Imperative: Manually update DOM (old way, error-prone).
Functional Components (Modern)
function Button({ label, onClick }) {
return (
<button onClick={onClick}>
{label}
</button>
);
}
<Button label="Click me" onClick={() => console.log('clicked')} />
Functional components are the standard now. Class components are legacy.
Hooks: The Foundation
useState: Managing Component State
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Key points:
useState returns [value, setter]
- Setter triggers re-render
- Initial value can be a function (useful for expensive calculations)
useEffect: Side Effects
import { useEffect, useState } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
return <div>{data}</div>;
}
Dependency array patterns:
[] (empty): Run once on mount
[dep1, dep2]: Run when deps change
- No array: Run after every render (rarely needed, performance killer)
useCallback: Memoizing Functions
import { useCallback, useState } from 'react';
function Parent() {
const [count, setCount] = useState(0);
const handleClick = () => {
console.log('clicked', count);
};
const handleClickMemo = useCallback(() => {
console.log('clicked', count);
}, [count]);
return <Child onClick={handleClickMemo} />;
}
When to use:
- Passing function to memoized child
- Using function as dependency in other hooks
- Performance-critical code paths
When NOT to use:
- Passing to non-memoized children (wastes memory)
- Debugging (adds complexity)
- Simple inline handlers
useMemo: Memoizing Values
import { useMemo, useState } from 'react';
function ExpensiveCalculation({ items }) {
const [sort, setSort] = useState('asc');
const sorted = items
.filter(item => item.active)
.sort((a, b) => sort === 'asc' ? a.value - b.value : b.value - a.value);
const sortedMemo = useMemo(() => {
return items
.filter(item => item.active)
.sort((a, b) => sort === 'asc' ? a.value - b.value : b.value - a.value);
}, [items, sort]);
return <div>{sortedMemo.length} items</div>;
}
When to use:
- Expensive calculations (sorting, filtering large arrays)
- Dependency for other memoized values
- Preventing child re-renders
When NOT to use:
- Simple calculations (wastes memory)
- Rarely-changing data
useRef: Accessing DOM Directly
import { useRef } from 'react';
function TextInput() {
const inputRef = useRef(null);
const focus = () => {
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} />
<button onClick={focus}>Focus input</button>
</>
);
}
When to use:
- Managing focus, text selection, media playback
- Triggering animations
- Integrating third-party libraries
When NOT to use:
- State management (use useState)
- Rendering data (use state)
Component Patterns
Controlled vs Uncontrolled Inputs
Controlled (React manages state):
function Form() {
const [email, setEmail] = useState('');
return (
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
);
}
Uncontrolled (DOM manages state):
function Form() {
const inputRef = useRef(null);
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return (
<>
<input ref={inputRef} />
<button onClick={handleSubmit}>Submit</button>
</>
);
}
Use controlled for forms. Uncontrolled only for file inputs and integrations.
Lifting State Up
When multiple components need shared state:
function Parent() {
return (
<>
<FirstChild />
<SecondChild />
</>
);
}
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<FirstChild count={count} />
<SecondChild count={count} onIncrement={() => setCount(count + 1)} />
</>
);
}
Custom Hooks
Encapsulate stateful logic:
function useFormInput(initialValue = '') {
const [value, setValue] = useState(initialValue);
return {
value,
setValue,
bind: {
value,
onChange: (e) => setValue(e.target.value),
},
};
}
function Form() {
const email = useFormInput('');
const password = useFormInput('');
return (
<form>
<input {...email.bind} placeholder="Email" />
<input type="password" {...password.bind} placeholder="Password" />
</form>
);
}
State Management
Context API
For prop drilling (passing props through many levels):
import { createContext, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
function Button() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme}>
Current theme: {theme}
</button>
);
}
When to use Context:
- Theme, language, user authentication
- Moderate-complexity state
- Avoid if state updates frequently (performance hit)
Zustand (Simple Global State)
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
function Counter() {
const { count, increment, decrement } = useStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
When to use Zustand:
- Simple, lightweight global state
- Minimal boilerplate
- Predictable performance
Redux (Complex State)
import { createSlice, configureStore } from '@reduxjs/toolkit';
import { useDispatch, useSelector } from 'react-redux';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
function Counter() {
const dispatch = useDispatch();
const count = useSelector((state) => state.counter.value);
return (
<div>
<p>Count: {count}</p>
<button = => dispatch(counterSlice.actions.increment())}>+
);
}
When to use Redux:
- Complex, interconnected state
- Many actions/reducers
- DevTools debugging needed
Performance Optimization
React.memo: Prevent Unnecessary Re-renders
import { memo } from 'react';
const Button = memo(({ label, onClick }) => {
console.log('Button rendered');
return <button onClick={onClick}>{label}</button>;
});
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<Button label="Increment" onClick={() => setCount(count + 1)} />
</div>
);
}
When to use:
- Heavy components (lists, charts)
- Props rarely change
- Profiler shows it's needed
Code Splitting with Dynamic Imports
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
Reduces initial bundle size. Load features on-demand.
Avoid Inline Objects in Dependencies
useEffect(() => {
}, [{ retry: true }]);
const config = { retry: true };
useEffect(() => {
}, [config]);
useEffect(() => {
}, [useMemo(() => ({ retry: true }), [])]);
Testing React Components
Testing Patterns
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
describe('Button', () => {
it('renders with label', () => {
render(<Button label="Click me" />);
expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const onClick = jest.fn();
render(<Button label="Click" onClick={onClick} />);
fireEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalled();
});
it('updates count on click', () => {
render(<Counter />);
button = screen.(, { : });
fireEvent.(button);
(screen.()).();
});
});
Best practices:
- Test user behavior, not implementation
- Use
screen queries (more resilient)
- Test accessibility (
getByRole uses ARIA)
- Mock external APIs
Common Mistakes
❌ Modifying State Directly
const [items, setItems] = useState([]);
items.push(newItem);
setItems([...items, newItem]);
❌ Infinite Loops in useEffect
useEffect(() => {
setData(newData);
});
useEffect(() => {
setData(newData);
}, []);
useEffect(() => {
fetchData(id);
}, [id]);
❌ Using Index as Key in Lists
{items.map((item, index) => <Item key={index} />)}
{items.map((item) => <Item key={item.id} />)}
❌ Unnecessary Memoization
const SimpleText = memo(({ text }) => <p>{text}</p>);
const DataTable = memo(({ rows }) => <table>{/* expensive render */}</table>);
When to Use This Skill
✅ Do use for:
- React component patterns and best practices
- Hooks (useState, useEffect, useCallback, useMemo, useRef)
- State management approaches (Context, Zustand, Redux)
- Component composition and lifting state
- Performance optimization (React.memo, code splitting, memoization)
- Custom hooks development
- Testing React components
- Framework selection guidance
❌ Don't use for:
- CSS styling (see css-styling-pixel-perfect)
- Design system setup (see design-systems-architecture)
- Responsive design (see responsive-universal-design)
- Build tools and bundlers
- Node.js backend frameworks
- HTML/semantic markup (see web-accessibility-a11y)