What is the difference between functional and class components?

👨‍💻 Frontend Developer 🟡 Often Asked 🎚️ Medium
#React

Brief Answer

Functional and class components are two ways to create components in React, but with key differences:

CharacteristicFunctionalClass
SyntaxSimpler and shorterMore verbose
StateHooks (useState)this.state
LifecycleHooks (useEffect)Class methods
PerformanceBetterWorse
thisNot requiredRequired

Modern approach:

  • Functional components - preferred way
  • Class components - legacy but still supported

Full Answer

In React there are two ways to create components: functional and class components. Although both approaches create components, they have significant differences in syntax, capabilities, and development approaches.

What are functional components

Functional components are regular JavaScript functions that accept props and return JSX:

// Functional component
import { useState } from 'react';
 
function Welcome({ name }) {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Counter: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
}

What are class components

Class components are ES6 classes that extend React.Component:

// Class component
import { Component } from 'react';
 
class Welcome extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  render() {
    return (
      <div>
        <h1>Hello, {this.props.name}!</h1>
        <p>Counter: {this.state.count}</p>
        <button onClick={() => this.setState({ 
          count: this.state.count + 1 
        })}>
          Increase
        </button>
      </div>
    );
  }
}

Key Differences

1. Syntax

Functional components are simpler and shorter:

// Functional component
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

Class components are more verbose:

// Class component
class Greeting extends Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

2. Working with State

Functional components use hooks:

import { useState } from 'react';
 
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Counter: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
}

Class components use this.state:

import { Component } from 'react';
 
class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  render() {
    return (
      <div>
        <p>Counter: {this.state.count}</p>
        <button onClick={() => this.setState({ 
          count: this.state.count + 1 
        })}>
          Increase
        </button>
      </div>
    );
  }
}

3. Lifecycle

Functional components use hooks:

import { useEffect, useState } from 'react';
 
function DataFetcher({ userId }) {
  const [data, setData] = useState(null);
  
  // Replaces componentDidMount, componentDidUpdate, componentWillUnmount
  useEffect(() => {
    fetchData(userId).then(setData);
    
    // Cleanup (componentWillUnmount)
    return () => {
      // Cancel requests, clear timers
    };
  }, [userId]); // Dependencies
  
  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}

Class components use lifecycle methods:

import { Component } from 'react';
 
class DataFetcher extends Component {
  constructor(props) {
    super(props);
    this.state = { data: null };
  }
  
  // componentDidMount
  componentDidMount() {
    this.fetchData(this.props.userId);
  }
  
  // componentDidUpdate
  componentDidUpdate(prevProps) {
    if (prevProps.userId !== this.props.userId) {
      this.fetchData(this.props.userId);
    }
  }
  
  // componentWillUnmount
  componentWillUnmount() {
    // Cancel requests, clear timers
  }
  
  fetchData = async (userId) => {
    const data = await fetchData(userId);
    this.setState({ data });
  }
  
  render() {
    return (
      <div>
        {this.state.data ? JSON.stringify(this.state.data) : 'Loading...'}
      </div>
    );
  }
}

4. Performance

Functional components are usually faster:

// Functional component - less overhead
function FastComponent({ data }) {
  return (
    <ul>
      {data.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Class components have more overhead:

// Class component - more overhead
class SlowerComponent extends Component {
  render() {
    return (
      <ul>
        {this.props.data.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    );
  }
}

When to Use Each Approach

Functional Components (Preferred Approach)

Use always when possible:

  • Modern React standard
  • Better code readability
  • Less boilerplate code
  • Better performance
  • Hooks support
// Recommended approach
import { useState, useEffect } from 'react';
 
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchUser = async () => {
      setLoading(true);
      const userData = await fetch(`/api/users/${userId}`).then(r => r.json());
      setUser(userData);
      setLoading(false);
    };
    
    fetchUser();
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  if (!user) return <div>User not found</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Class Components (Legacy Approach)

Use only when necessary:

  • Supporting legacy code
  • Working with legacy projects
  • Specific cases where hooks don’t fit
// Only for legacy code
import { Component } from 'react';
 
class LegacyUserProfile extends Component {
  constructor(props) {
    super(props);
    this.state = { user: null, loading: true };
  }
  
  componentDidMount() {
    this.fetchUser();
  }
  
  componentDidUpdate(prevProps) {
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser();
    }
  }
  
  fetchUser = async () => {
    this.setState({ loading: true });
    const userData = await fetch(`/api/users/${this.props.userId}`).then(r => r.json());
    this.setState({ user: userData, loading: false });
  }
  
  render() {
    if (this.state.loading) return <div>Loading...</div>;
    if (!this.state.user) return <div>User not found</div>;
    
    return (
      <div>
        <h1>{this.state.user.name}</h1>
        <p>{this.state.user.email}</p>
      </div>
    );
  }
}

Practical Examples

1. Simple Component

Functional Approach:

import { useState } from 'react';
 
function Toggle() {
  const [isOn, setIsOn] = useState(false);
  
  return (
    <button onClick={() => setIsOn(!isOn)}>
      {isOn ? 'ON' : 'OFF'}
    </button>
  );
}

Class Approach:

import { Component } from 'react';
 
class Toggle extends Component {
  constructor(props) {
    super(props);
    this.state = { isOn: false };
  }
  
  render() {
    return (
      <button onClick={() => this.setState({ 
        isOn: !this.state.isOn 
      })}>
        {this.state.isOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

2. Component with Side Effects

Functional Approach:

import { useEffect, useState } from 'react';
 
function WindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  
  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };
    
    window.addEventListener('resize', handleResize);
    
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
  
  return (
    <div>
      <p>Width: {size.width}px</p>
      <p>Height: {size.height}px</p>
    </div>
  );
}

Class Approach:

import { Component } from 'react';
 
class WindowSize extends Component {
  constructor(props) {
    super(props);
    this.state = {
      width: window.innerWidth,
      height: window.innerHeight
    };
  }
  
  componentDidMount() {
    window.addEventListener('resize', this.handleResize);
  }
  
  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize);
  }
  
  handleResize = () => {
    this.setState({
      width: window.innerWidth,
      height: window.innerHeight
    });
  }
  
  render() {
    return (
      <div>
        <p>Width: {this.state.width}px</p>
        <p>Height: {this.state.height}px</p>
      </div>
    );
  }
}

Summary

Functional and class components are two ways to create components in React:

Functional components (modern approach):

  • Simpler and shorter
  • Use hooks for state and effects
  • Better performance
  • Preferred way in modern React

Class components (legacy approach):

  • More verbose
  • Use this.state and lifecycle methods
  • More overhead
  • Used only in legacy code

Key points:

  • Functional components became the standard with the introduction of hooks
  • Class components are still supported but not recommended
  • Hooks provide a cleaner and more flexible way to work with state
  • For new projects, always use functional components

Understanding the difference between these approaches will help you choose the right way to create components and update legacy code when necessary.


Want more articles for interview preparation? Subscribe to EasyAdvice, bookmark the site, and improve every day 💪