Learn the conventions, patterns, and anti-patterns that distinguish professional React code from amateur code, covering component organization, accessibility, performance, and maintainability.
How you organize your files has a direct impact on how quickly you can navigate, understand, and maintain your codebase. There is no single "correct" structure, but two patterns have proven effective at scale:
1. Feature-based structure (recommended for apps):
Group files by feature, not by type. Each feature folder contains everything it needs:
src/
features/
auth/
LoginForm.tsx
SignupForm.tsx
useAuth.ts
auth.test.ts
auth.types.ts
dashboard/
Dashboard.tsx
DashboardChart.tsx
useDashboardData.ts
shared/
components/
Button.tsx
Modal.tsx
hooks/
useDebounce.ts
useLocalStorage.ts2. Flat component structure (for smaller projects):
src/
components/
Button/
Button.tsx
Button.test.tsx
Button.module.css
index.ts
Header/
Header.tsx
Header.test.tsx
index.tsKey conventions:
UserCard.tsx exports UserCard.index.ts barrel file to simplify imports: import { Button } from '@/components/Button'.Consistent naming makes your codebase readable without documentation:
Components: PascalCase. Name them after what they render, not what they do.
UserCard.tsx (good — describes what it renders)
FetchUser.tsx (bad — describes implementation)Hooks: camelCase with use prefix. Name them after what they provide.
useAuth() (good — provides auth state and actions)
useLocalStorage() (good — provides localStorage access)
useFetch() (ok — generic, but clear)Event handlers: handle + event name in the component, on + event name for props.
// Inside the component
function SearchBar({ onSearch }) {
const handleSubmit = (e) => {
e.preventDefault();
onSearch(query);
};
// ...
}Boolean props: Use is, has, or should prefixes.
<Modal isOpen={true} hasOverlay={true} shouldCloseOnEscape={true} />The Single Responsibility Principle states that each component should have one reason to change. Signs your component does too much:
Extract logic into custom hooks. Extract UI into sub-components. The result is components that are easier to test, reuse, and reason about.
One of the biggest sources of complexity in React apps is mismanaged state. Follow these guidelines to keep state predictable:
1. Start with local state.
Always start with useState in the component that needs the data. Only lift state up when siblings need to share it.
// Good — state is local to the component that uses it
function SearchBar() {
const [query, setQuery] = React.useState('');
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}2. Lift state only when necessary. When two sibling components need the same data, move state to their closest common parent:
function App() {
const [user, setUser] = React.useState(null);
return (
<>
<Header user={user} />
<Profile user={user} onUpdate={setUser} />
</>
);
}3. Use context for deeply shared state. When data needs to flow through many levels (theme, locale, auth), use the Provider pattern:
<AuthProvider>
<ThemeProvider>
<App />
</ThemeProvider>
</AuthProvider>4. Use external stores for complex state. When state logic becomes complex (many actions, derived state, async flows), use a state management library like Zustand or Redux Toolkit.
5. Derive state, do not sync it. If you can compute a value from existing state, do not store it separately:
// Bad — syncing derived state
const [items, setItems] = React.useState([]);
const [count, setCount] = React.useState(0); // redundant!
// Good — derive it
const [items, setItems] = React.useState([]);
const count = items.length; // computed on every render6. Avoid state for ref-like values.
Values that should not trigger re-renders (timer IDs, previous values, DOM references) belong in useRef, not useState.
Accessibility (a11y) is not an afterthought — it is a core quality requirement. React makes it easy to build accessible UIs when you follow these practices:
1. Use semantic HTML elements:
// Bad
<div onClick={handleClick}>Click me</div>
// Good
<button onClick={handleClick}>Click me</button>Semantic elements come with built-in keyboard handling, focus management, and screen reader announcements. A <button> is focusable, responds to Enter/Space, and is announced as a button. A <div> does none of that.
2. Always provide text alternatives:
<img src="avatar.jpg" alt="Jane's profile photo" />
<button aria-label="Close dialog"><XIcon /></button>3. Structure pages with landmarks:
function App() {
return (
<>
<header><h1>My App</h1></header>
<nav><a href="/">Home</a></nav>
<main><p>Content goes here</p></main>
<footer><p>Copyright info</p></footer>
</>
);
}Screen readers use these landmarks (<header>, <nav>, <main>, <footer>) to let users jump between sections of the page.
4. Manage focus for dynamic content: When a modal opens, move focus into it. When it closes, return focus to the trigger element:
const triggerRef = React.useRef();
const modalRef = React.useRef();
const openModal = () => {
setIsOpen(true);
// Focus the modal after render
setTimeout(() => modalRef.current?.focus(), 0);
};
const closeModal = () => {
setIsOpen(false);
triggerRef.current?.focus(); // Return focus
};5. Use ARIA attributes when native HTML is insufficient:
<div role="alert" aria-live="polite">
{errorMessage}
</div>
<button aria-expanded={isOpen} aria-controls="menu-content">
Menu
</button>
<ul id="menu-content" role="menu" hidden={!isOpen}>
<li role="menuitem">Option 1</li>
</ul>6. Test with a keyboard. Navigate your entire UI using only Tab, Shift+Tab, Enter, Space, Escape, and arrow keys. If something is not reachable or operable, fix it.
Recognizing anti-patterns is as important as knowing best practices. Here are the most common mistakes:
1. Prop drilling through many layers:
// Bad — passing user through 5 levels
<App user={user}>
<Layout user={user}>
<Sidebar user={user}>
<UserMenu user={user} />
</Sidebar>
</Layout>
</App>
// Good — use context
<UserProvider>
<App />
</UserProvider>2. Huge monolithic components: If your component file is 500+ lines, it is doing too much. Extract sub-components and hooks.
3. Unnecessary useEffect:
// Bad — useEffect to transform data
useEffect(() => {
setFilteredItems(items.filter(i => i.active));
}, [items]);
// Good — compute during render
const filteredItems = items.filter(i => i.active);useEffect is for synchronizing with external systems (API calls, subscriptions, DOM manipulation). If you are using it to derive state, you are overcomplicating things.
4. Missing keys or using index as key:
// Bad — index keys cause bugs when list order changes
{items.map((item, index) => <Item key={index} {...item} />)}
// Good — use a stable, unique identifier
{items.map(item => <Item key={item.id} {...item} />)}5. Direct DOM manipulation:
// Bad — bypassing React
document.getElementById('title').textContent = 'New Title';
// Good — let React manage the DOM
setTitle('New Title');6. Not cleaning up side effects:
// Bad — memory leak
useEffect(() => {
window.addEventListener('resize', handleResize);
}, []);
// Good — cleanup function
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);Performance checklist:
useMemo.useCallback.React.memo for components that re-render with the same props.React.lazy and Suspense.<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// Each component has a single responsibility
// Semantic HTML elements for accessibility
// Clear naming conventions
function Header() {
return (
<header id="header">
<h1>My App</h1>
</header>
);
}
function Main() {
return (
<main id="main">
<p>Content</p>
</main>
);
}
function Footer() {
return (
<footer id="footer">
<p>© 2024</p>
</footer>
);
}
// App composes independent components
function App() {
return (
<>
<Header />
<Main />
<Footer />
</>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>Which of the following is an anti-pattern in React?