Asher Cohen
Back to posts

React Best Practices: Components, Hooks, and Performance

Comprehensive guide to React development — component patterns, hooks, performance optimization, and code organization

Introduction

React is a JavaScript library for building user interfaces. Writing clean, performant React code requires understanding component architecture, hooks, state management, and performance patterns. This guide covers the essential practices for professional React development.

Component Architecture

Functional Components

Always prefer functional components over class components:

// ❌ Class component (avoid)
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

// ✅ Functional component (preferred)
function Welcome({ name }) {
  return <h1>Hello, {name}</h1>;
}

Component Composition

Compose small, focused components rather than building monoliths:

// ❌ Monolithic component
function Dashboard() {
  return (
    <div>
      <header>{/* 50 lines of header */}</header>
      <aside>{/* 30 lines of sidebar */}</aside>
      <main>{/* 100 lines of content */}</main>
      <footer>{/* 20 lines of footer */}</footer>
    </div>
  );
}

// ✅ Composed from smaller components
function Dashboard() {
  return (
    <div>
      <DashboardHeader />
      <DashboardSidebar />
      <DashboardContent />
      <DashboardFooter />
    </div>
  );
}

Props Typing

Always type your props with TypeScript or PropTypes:

interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
  children?: React.ReactNode;
}

function Button({ label, onClick, variant = 'primary', disabled, children }: ButtonProps) {
  return (
    <button onClick={onClick} disabled={disabled} className={`btn btn-${variant}`}>
      {children || label}
    </button>
  );
}

Hooks

useState

// Basic state
const [count, setCount] = useState(0);

// Functional update (when new state depends on previous)
setCount(prev => prev + 1);

// Lazy initialization (expensive computation)
const [data, setData] = useState(() => computeExpensiveInitialValue());

// Object state
const [user, setUser] = useState({ name: '', email: '' });
setUser(prev => ({ ...prev, name: 'Alice' }));

useEffect

Use sparingly — most logic belongs in event handlers:

// ✅ Sync with external system (browser API)
useEffect(() => {
  document.title = `Count: ${count}`;
}, [count]);

// ✅ Event subscription with cleanup
useEffect(() => {
  const handler = (e) => handleKeyPress(e);
  window.addEventListener('keydown', handler);
  return () => window.removeEventListener('keydown', handler);
}, []);

// ❌ Don't use useEffect for derived state
// Derive values during render instead
const fullName = `${firstName} ${lastName}`; // Not in useEffect!

useMemo and useCallback

Use for expensive computations and stable references:

// ✅ useMemo: cache expensive computation
const sortedList = useMemo(() => {
  return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);

// ✅ useCallback: stable function reference
const handleClick = useCallback((id) => {
  setSelected(prev => prev === id ? null : id);
}, []);

// ❌ Don't memoize everything — only when needed
// Simple computations don't need useMemo

Custom Hooks

Extract reusable logic into custom hooks:

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });
  
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  
  return [value, setValue];
}

// Usage
function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  return <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>{theme}</button>;
}

Performance

Avoid Unnecessary Re-renders

// ✅ React.memo for pure components
const UserCard = React.memo(function UserCard({ user }) {
  return <div>{user.name}</div>;
});

// ✅ Move state down to isolate re-renders
function Parent() {
  return (
    <div>
      <ExpensiveChild />
      <Counter /> {/* State changes only affect Counter */}
    </div>
  );
}

// ✅ Use key to reset component state
function UserProfile({ userId }) {
  return <Profile key={userId} userId={userId} />;
}

Lazy Loading

import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Skeleton />}>
      <HeavyComponent />
    </Suspense>
  );
}

State Management

Lifting State Up

// ✅ Shared state goes to closest common ancestor
function Parent() {
  const [selected, setSelected] = useState(null);
  
  return (
    <>
      <List items={items} selected={selected} onSelect={setSelected} />
      <Detail item={selected} />
    </>
  );
}

Context for Global State

const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Content />
    </ThemeContext.Provider>
  );
}

function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  return <button className={theme}>Toggle</button>;
}

Code Organization

File Structure

src/
  components/
    Button/
      Button.tsx
      Button.test.tsx
      Button.module.css
    UserCard/
      UserCard.tsx
      UserCard.test.tsx
  hooks/
    useLocalStorage.ts
    useDebounce.ts
  utils/
    formatDate.ts
    api.ts
  pages/
    Home.tsx
    About.tsx

Naming Conventions

  • Components: PascalCase files (UserCard.tsx)
  • Hooks: camelCase with use prefix (useLocalStorage.ts)
  • Utilities: camelCase (formatDate.ts)
  • One component per file (with co-located tests)

Best Practices

Do's

// ✅ Keep components small and focused
// ✅ Use TypeScript for props
// ✅ Derive state during render, not in effects
// ✅ Handle loading, error, and empty states
// ✅ Use semantic HTML and ARIA attributes
// ✅ Co-locate tests with components

Don'ts

// ❌ Don't mutate state directly
state.value = newValue; // Wrong!

// ❌ Don't use useEffect for derived state
useEffect(() => setFullName(first + ' ' + last), [first, last]);

// ❌ Don't create components inside components
function Parent() {
  function Child() { return <div />; } // New reference every render!
}

// ❌ Don't ignore keys in lists
items.map(item => <div>{item.name}</div>); // Missing key!

// ❌ Don't over-memoize
const doubled = useMemo(() => count * 2, [count]); // Unnecessary

Summary Table

ConceptPatternWhen to Use
ComponentsFunctional + TypeScriptAlways
StateuseStateComponent-local state
Side effectsuseEffectBrowser APIs, subscriptions
MemoizationuseMemo/useCallbackExpensive computations
ReusabilityCustom hooksShared logic
PerformanceReact.memoPure components
Code splittinglazy + SuspenseLarge components
Global stateContext or ZustandShared app state

Conclusion

Professional React development is about:

  • Small, focused components that compose well
  • Hooks for state and side effects (used sparingly)
  • TypeScript for type safety and documentation
  • Performance awareness without premature optimization
  • Clean code organization with consistent naming

Master these patterns and you'll build React applications that are maintainable, performant, and a joy to work with.

#react #typescript #javascript #hooks #performance #web-development #frontend