Skip to content

Function Components

Function components are the standard way to write React in 2024 and beyond. Combined with hooks, they replace class components for state, lifecycle, and side effects. This lesson explains why and how to write them effectively.

Why Function Components Won

Before hooks (React 16.8), only class components could hold state and lifecycle methods. Hooks brought that capability to plain functions, resulting in less boilerplate, easier testing, and better TypeScript inference.

The React team no longer recommends class components for new code. Libraries, docs, and tooling assume function components and hooks. You may still encounter classes in legacy codebases.

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const id = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(id);
  }, []);

  return <p>Elapsed: {seconds}s</p>;
}

State and lifecycle in one concise function — no this, no constructor, no binding.

Function Component Anatomy

function MyComponent({ propA, propB = 'default' }) {
  // 1. Hooks (always at top level)
  const [state, setState] = useState(initial);

  // 2. Event handlers and helpers
  const handleClick = () => setState(x => x + 1);

  // 3. Return JSX
  return <button onClick={handleClick}>{state}</button>;
}
  • Destructure props in the parameter list for clarity.
  • Call hooks only at the top level — never inside conditions or loops.
  • Return JSX (or null to render nothing).
  • Use arrow functions or function declarations — both are valid.

Function vs Class Components

How modern function components map to legacy class patterns.

Class (legacy) Function + Hooks (modern)
this.state useState()
componentDidMount useEffect(() => {}, [])
componentDidUpdate useEffect(() => {}, [dep])
componentWillUnmount useEffect cleanup return
this.setState setState from useState
Context Consumer useContext()
ref callback useRef()

Arrow Functions vs Function Declarations

Both function Button() {} and const Button = () => {} work for components. Function declarations are hoisted; arrow components are not. Teams often prefer function for top-level components and arrows for inline callbacks.

When You Still See Class Components

Legacy apps, older tutorials, and some error boundaries (only classes could implement componentDidCatch until recently) may use classes. React 19 adds function component error boundaries in some setups, but many codebases still use class error boundaries.

  • Maintaining existing class-based code without full rewrites.
  • Some third-party libraries document class-based examples.
  • Error boundaries historically required classes (check your React version).
  • New features should always use function components.

Common Mistakes

  • Mixing class lifecycle mental models with hooks incorrectly.
  • Using this in function components — it does not refer to the component instance.
  • Converting classes to functions line-by-line instead of rethinking state with hooks.
  • Calling hooks conditionally inside function components.

Key Takeaways

  • Function components with hooks are the modern React standard.
  • Hooks replace state, lifecycle, context, and refs from class components.
  • Less boilerplate and better composability than classes.
  • Use function components for all new React code.

Pro Tip

When reading legacy class code, map each lifecycle method to the equivalent useEffect pattern before rewriting.