Category: development
Purpose: Modern React patterns, hooks usage, and component design principles
Used by: frontend-specialist
This guide covers modern React patterns using functional components, hooks, and best practices for building scalable React applications.
Always use functional components:
// Good
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
return <div>{user?.name}</div>;
}
Extract common logic into custom hooks:
// Custom hook
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchUser(userId)
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
return { user, loading, error };
}
// Usage
function UserProfile({ userId }) {
const { user, loading, error } = useUser(userId);
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <div>{user.name}</div>;
}
Use composition to avoid prop drilling:
// Bad - Props drilling
function App() {
const [theme, setTheme] = useState('light');
return <Layout theme={theme} setTheme={setTheme} />;
}
// Good - Composition with Context
const ThemeContext = createContext();
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Layout />
</ThemeContext.Provider>
);
}
function Layout() {
const { theme } = useContext(ThemeContext);
return <div className={theme}>...</div>;
}
For complex, related components:
function Tabs({ children }) {
const [activeTab, setActiveTab] = useState(0);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
{children}
</TabsContext.Provider>
);
}
Tabs.List = function TabsList({ children }) {
return <div className="tabs-list">{children}</div>;
};
Tabs.Tab = function Tab({ index, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
return (
<button
className={activeTab === index ? 'active' : ''}
onClick={() => setActiveTab(index)}
>
{children}
</button>
);
};
Tabs.Panel = function TabPanel({ index, children }) {
const { activeTab } = useContext(TabsContext);
return activeTab === index ? <div>{children}</div> : null;
};
// Usage
<Tabs>
<Tabs.List>
<Tabs.Tab index={0}>Tab 1</Tabs.Tab>
<Tabs.Tab index={1}>Tab 2</Tabs.Tab>
</Tabs.List>
<Tabs.Panel index={0}>Content 1</Tabs.Panel>
<Tabs.Panel index={1}>Content 2</Tabs.Panel>
</Tabs>
Always specify dependencies correctly:
// Bad - Missing dependencies
useEffect(() => {
fetchData(userId);
}, []);
// Good - Correct dependencies
useEffect(() => {
fetchData(userId);
}, [userId]);
// Good - Stable function reference
const fetchData = useCallback((id) => {
api.getUser(id).then(setUser);
}, []);
useEffect(() => {
fetchData(userId);
}, [userId, fetchData]);
Memoize expensive computations:
function DataTable({ data, filters }) {
const filteredData = useMemo(() => {
return data.filter(item =>
filters.every(filter => filter(item))
);
}, [data, filters]);
return <Table data={filteredData} />;
}
Prevent unnecessary re-renders:
function Parent() {
const [count, setCount] = useState(0);
// Bad - New function on every render
const handleClick = () => setCount(c => c + 1);
// Good - Stable function reference
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return <Child onClick={handleClick} />;
}
const Child = memo(function Child({ onClick }) {
return <button onClick={onClick}>Click</button>;
});
Start with local state, lift when needed:
// Local state
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// Lifted state when shared
function App() {
const [count, setCount] = useState(0);
return (
<>
<Counter count={count} setCount={setCount} />
<Display count={count} />
</>
);
}
Use reducer for related state updates:
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'setStep':
return { ...state, step: action.payload };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}
Lazy load routes and heavy components:
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
Use virtualization for large datasets:
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>{items[index].name}</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}