---
# System prepended metadata

title: React Component Lifecycle

---

# React Component Lifecycle

Understanding React component lifecycle is crucial for building robust React applications. This guide covers both class component lifecycle methods and functional component lifecycle patterns using hooks.

## Introduction

React components go through different phases during their lifetime:

1. **Mounting** - Component is created and inserted into the DOM
2. **Updating** - Component re-renders due to state or prop changes
3. **Unmounting** - Component is removed from the DOM

## Class Component Lifecycle

### Mounting Phase

#### constructor()

Called before the component is mounted. Used for initializing state and binding methods.

```javascript
class Counter extends Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
    this.handleClick = this.handleClick.bind(this)
  }
}
```

#### `componentDidMount()`

Called after the component is mounted to the DOM. Perfect for side effects like API calls, setting up subscriptions, or DOM manipulation.

```javascript
class ChatRoom extends Component {
  componentDidMount() {
    this.setupConnection()
    this.logVisit()
  }

  setupConnection() {
    this.connection = createConnection(this.props.roomId)
    this.connection.connect()
  }
}
```

### Updating Phase

#### `componentDidUpdate(prevProps, prevState)`

Called after the component updates. Use for side effects that depend on prop or state changes.

```javascript
class ChatRoom extends Component {
  componentDidUpdate(prevProps, prevState) {
    if (this.props.roomId !== prevProps.roomId) {
      this.destroyConnection()
      this.setupConnection()
    }
  }
}
```

#### `shouldComponentUpdate(nextProps, nextState)`

Controls whether the component should re-render. Return `false` to prevent unnecessary re-renders.

```javascript
class Rectangle extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    if (
      nextProps.position.x === this.props.position.x &&
      nextProps.position.y === this.props.position.y &&
      nextState.isHovered === this.state.isHovered
    ) {
      return false // No changes, skip re-render
    }
    return true
  }
}
```

### Unmounting Phase

#### `componentWillUnmount()`

Called before the component is unmounted. Use for cleanup like canceling timers, removing event listeners, or cleaning up subscriptions.

```javascript
class ChatRoom extends Component {
  componentWillUnmount() {
    this.destroyConnection()
  }

  destroyConnection() {
    if (this.connection) {
      this.connection.disconnect()
      this.connection = null
    }
  }
}
```

## Functional Component Lifecycle

### `useEffect` Hook

The `useEffect` hook replaces most lifecycle methods in functional components.

#### Mount and Unmount (componentDidMount + componentWillUnmount)

```javascript
function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId)
    connection.connect()

    return () => {
      connection.disconnect()
    }
  }, []) // Empty dependency array = run once on mount
}
```

#### Effect with Dependencies (componentDidUpdate)

```javascript
function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId)
    connection.connect()

    return () => {
      connection.disconnect()
    }
  }, [roomId]) // Re-run when roomId changes
}
```

#### Multiple Effects

```javascript
function ChatRoom({ roomId }) {
  // Separate concerns into different effects
  useEffect(() => {
    logVisit(roomId)
  }, [roomId])

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId)
    connection.connect()

    return () => {
      connection.disconnect()
    }
  }, [roomId])
}
```

## Lifecycle Phases

### Component Lifecycle Diagram

```mermaid
graph TD
    A[Component Created] --> B[Constructor]
    B --> C[Render]
    C --> D[componentDidMount]
    D --> E[Component Mounted]

    E --> F[Props/State Change]
    F --> G[shouldComponentUpdate]
    G -->|true| H[Render]
    G -->|false| E
    H --> I[componentDidUpdate]
    I --> E

    E --> J[Component Unmounting]
    J --> K[componentWillUnmount]
    K --> L[Component Unmounted]

    style A fill:#e1f5fe
    style E fill:#c8e6c9
    style L fill:#ffcdd2
```

### Functional Component Lifecycle Diagram

```mermaid
graph TD
    A[Function Called] --> B[useState/useEffect Setup]
    B --> C[Render]
    C --> D[useEffect Runs]
    D --> E[Component Mounted]

    E --> F[Props/State Change]
    F --> G[Function Called Again]
    G --> H[useEffect Dependencies Check]
    H -->|Dependencies Changed| I[Cleanup Previous Effect]
    H -->|No Changes| J[Skip Effect]
    I --> K[Run New Effect]
    J --> L[Render]
    K --> L
    L --> E

    E --> M[Component Unmounting]
    M --> N[useEffect Cleanup]
    N --> O[Component Unmounted]

    style A fill:#e1f5fe
    style E fill:#c8e6c9
    style O fill:#ffcdd2
```

## Real-World Examples

### Chat Application with Connection Management

```javascript
// Class Component Version
class ChatRoom extends Component {
  state = {
    serverUrl: 'https://localhost:1234',
    messages: []
  }

  componentDidMount() {
    this.setupConnection()
  }

  componentDidUpdate(prevProps, prevState) {
    if (
      this.props.roomId !== prevProps.roomId ||
      this.state.serverUrl !== prevState.serverUrl
    ) {
      this.destroyConnection()
      this.setupConnection()
    }
  }

  componentWillUnmount() {
    this.destroyConnection()
  }

  setupConnection() {
    this.connection = createConnection(this.state.serverUrl, this.props.roomId)
    this.connection.connect()
  }

  destroyConnection() {
    if (this.connection) {
      this.connection.disconnect()
      this.connection = null
    }
  }

  render() {
    return (
      <>
        <label>
          Server URL:{' '}
          <input
            value={this.state.serverUrl}
            onChange={(e) => {
              this.setState({
                serverUrl: e.target.value
              })
            }}
          />
        </label>
        <h1>Welcome to the {this.props.roomId} room!</h1>
      </>
    )
  }
}
```

```javascript
// Functional Component Version
function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234')

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId)
    connection.connect()

    return () => {
      connection.disconnect()
    }
  }, [roomId, serverUrl])

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={(e) => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
    </>
  )
}
```

### Performance Optimization with shouldComponentUpdate

```javascript
class ExpensiveComponent extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    // Only re-render if specific props change
    return (
      nextProps.id !== this.props.id ||
      nextProps.name !== this.props.name ||
      nextState.isVisible !== this.state.isVisible
    )
  }

  render() {
    return (
      <div>
        <h2>{this.props.name}</h2>
        <p>ID: {this.props.id}</p>
        {this.state.isVisible && <p>This is expensive to render!</p>}
      </div>
    )
  }
}
```

### Timer Component with Cleanup

```javascript
// Class Component
class Timer extends Component {
  state = { seconds: 0 }

  componentDidMount() {
    this.interval = setInterval(() => {
      this.setState((prevState) => ({
        seconds: prevState.seconds + 1
      }))
    }, 1000)
  }

  componentWillUnmount() {
    clearInterval(this.interval)
  }

  render() {
    return <div>Seconds: {this.state.seconds}</div>
  }
}

// Functional Component
function Timer() {
  const [seconds, setSeconds] = useState(0)

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds((prev) => prev + 1)
    }, 1000)

    return () => clearInterval(interval)
  }, [])

  return <div>Seconds: {seconds}</div>
}
```

### Data Fetching with Loading States

```javascript
// Class Component
class UserProfile extends Component {
  state = { user: null, loading: true, error: null }

  async componentDidMount() {
    try {
      const response = await fetch(`/api/users/${this.props.userId}`)
      const user = await response.json()
      this.setState({ user, loading: false })
    } catch (error) {
      this.setState({ error: error.message, loading: false })
    }
  }

  render() {
    if (this.state.loading) return <div>Loading...</div>
    if (this.state.error) return <div>Error: {this.state.error}</div>
    return <div>Hello, {this.state.user.name}!</div>
  }
}

// Functional Component
function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)

  useEffect(() => {
    async function fetchUser() {
      try {
        const response = await fetch(`/api/users/${userId}`)
        const userData = await response.json()
        setUser(userData)
        setLoading(false)
      } catch (err) {
        setError(err.message)
        setLoading(false)
      }
    }

    fetchUser()
  }, [userId])

  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error}</div>
  return <div>Hello, {user.name}!</div>
}
```

## Best Practices

### 1. Use Functional Components with Hooks

Modern React development favors functional components with hooks over class components:

```javascript
// Preferred: Functional component
function MyComponent({ prop }) {
  const [state, setState] = useState(initialValue)

  useEffect(() => {
    // Side effects here
    return () => {
      // Cleanup here
    }
  }, [dependency])

  return <div>{state}</div>
}
```

### 2. Separate Concerns with Multiple useEffect

```javascript
function ChatRoom({ roomId }) {
  // Analytics effect
  useEffect(() => {
    logVisit(roomId)
  }, [roomId])

  // Connection effect
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId)
    connection.connect()

    return () => connection.disconnect()
  }, [roomId])

  // Notification effect
  useEffect(() => {
    if (roomId === 'urgent') {
      showNotification('Urgent room selected')
    }
  }, [roomId])
}
```

### 3. Always Clean Up Side Effects

```javascript
function MyComponent() {
  useEffect(() => {
    const subscription = subscribe()
    const timer = setInterval(doSomething, 1000)

    return () => {
      subscription.unsubscribe()
      clearInterval(timer)
    }
  }, [])
}
```

### 4. Use Dependency Arrays Correctly

```javascript
// Missing dependencies
useEffect(() => {
  fetchData(userId, serverUrl)
}, []) // Missing userId and serverUrl

// Correct dependencies
useEffect(() => {
  fetchData(userId, serverUrl)
}, [userId, serverUrl])
```

### 5. Avoid Stale Closures

```javascript
// Stale closure
function MyComponent() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1) // Always uses initial count value
    }, 1000)

    return () => clearInterval(timer)
  }, []) // Missing count dependency
}

// Correct approach
function MyComponent() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      setCount((prev) => prev + 1) // Uses current count value
    }, 1000)

    return () => clearInterval(timer)
  }, [])
}
```

## Migration from Class to Functional

### Step 1: Convert the Structure

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

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

### Step 2: Convert State

```javascript
// Before: Class component state
class Counter extends Component {
  state = { count: 0 }

  handleClick = () => {
    this.setState({ count: this.state.count + 1 })
  }
}

// After: Functional component with useState
function Counter() {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    setCount(count + 1)
  }
}
```

### Step 3: Convert Lifecycle Methods

```javascript
// Before: Class component lifecycle
class ChatRoom extends Component {
  componentDidMount() {
    this.setupConnection()
  }

  componentWillUnmount() {
    this.destroyConnection()
  }
}

// After: Functional component with useEffect
function ChatRoom() {
  useEffect(() => {
    setupConnection()

    return () => {
      destroyConnection()
    }
  }, [])
}
```

### Complete Migration Example

```javascript
// Before: Class component
class UserProfile extends Component {
  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 user = await getUser(this.props.userId)
    this.setState({ user, loading: false })
  }

  render() {
    if (this.state.loading) return <div>Loading...</div>
    return <div>{this.state.user.name}</div>
  }
}

// After: Functional component
function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    async function fetchUser() {
      setLoading(true)
      const userData = await getUser(userId)
      setUser(userData)
      setLoading(false)
    }

    fetchUser()
  }, [userId])

  if (loading) return <div>Loading...</div>
  return <div>{user.name}</div>
}
```

## Common Pitfalls

### 1. Missing Cleanup

```javascript
// Memory leak
useEffect(() => {
  const timer = setInterval(doSomething, 1000)
  // Missing cleanup!
}, [])

// Proper cleanup
useEffect(() => {
  const timer = setInterval(doSomething, 1000)
  return () => clearInterval(timer)
}, [])
```

### 2. Incorrect Dependencies

```javascript
// Missing dependencies
useEffect(() => {
  fetchData(prop1, prop2)
}, []) // Should include prop1, prop2

// Correct dependencies
useEffect(() => {
  fetchData(prop1, prop2)
}, [prop1, prop2])
```

### 3. Effect Running Too Often

```javascript
// Runs on every render
useEffect(() => {
  doSomething()
}) // Missing dependency array

// Runs only when needed
useEffect(() => {
  doSomething()
}, [dependency])
```

## Close it Out

Understanding React component lifecycle is essential for building performant and maintainable applications. While class components provide explicit lifecycle methods, functional components with hooks offer a more modern and flexible approach. The key is to:

1. **Use functional components** with hooks for new development
2. **Separate concerns** with multiple useEffect hooks
3. **Always clean up** side effects
4. **Use dependency arrays** correctly
5. **Avoid common pitfalls** like stale closures and memory leaks

♥️,
dh
