Skip to main content
Lifecycle methods are special methods that get called at different stages of a component’s life. They allow you to run code when a component mounts, updates, or unmounts.
Lifecycle methods are only available in class components. Functional components use hooks like useEffect instead.

Component lifecycle phases

A component goes through three main phases:
  1. Mounting - Component is created and inserted into the DOM
  2. Updating - Component re-renders due to prop or state changes
  3. Unmounting - Component is removed from the DOM

Mounting phase

These methods are called when a component is being created and inserted into the DOM.

constructor()

Called before the component is mounted. Used for initializing state and binding methods.
import { Component } from 'preact';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.increment = this.increment.bind(this);
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button onClick={this.increment}>
        Count: {this.state.count}
      </button>
    );
  }
}
Always call super(props) before accessing this.props in the constructor.

componentWillMount()

Deprecated - This method is called before mounting but is deprecated. Use constructor() or componentDidMount() instead.
Called immediately before mounting occurs. It’s invoked during the diff process (src/diff/index.js:168):
if (
  isClassComponent &&
  newType.getDerivedStateFromProps == NULL &&
  c.componentWillMount != NULL
) {
  c.componentWillMount();
}

componentDidMount()

Called immediately after the component is mounted. Perfect for:
  • Fetching data from APIs
  • Setting up subscriptions
  • Adding event listeners
  • Interacting with the DOM
Implementation in Preact (src/diff/index.js:173):
if (isClassComponent && c.componentDidMount != NULL) {
  c._renderCallbacks.push(c.componentDidMount);
}

Example usage

import { Component } from 'preact';

class UserProfile extends Component {
  state = { user: null, loading: true };

  componentDidMount() {
    fetch(`/api/users/${this.props.userId}`)
      .then(res => res.json())
      .then(user => {
        this.setState({ user, loading: false });
      });
  }

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

    return (
      <div>
        <h1>{this.state.user.name}</h1>
        <p>{this.state.user.email}</p>
      </div>
    );
  }
}

Updating phase

These methods are called when a component’s props or state change.

getDerivedStateFromProps()

Static method called before rendering, both on initial mount and on updates. Returns an object to update state, or null to update nothing. Implementation (src/diff/index.js:148):
if (isClassComponent && newType.getDerivedStateFromProps != NULL) {
  if (c._nextState == c.state) {
    c._nextState = assign({}, c._nextState);
  }

  assign(
    c._nextState,
    newType.getDerivedStateFromProps(newProps, c._nextState)
  );
}

Example usage

import { Component } from 'preact';

class EmailInput extends Component {
  state = { email: '', domain: '' };

  static getDerivedStateFromProps(props, state) {
    // Update domain when email changes
    if (props.email !== state.email) {
      const domain = props.email.split('@')[1] || '';
      return { email: props.email, domain };
    }
    return null;
  }

  render() {
    return (
      <div>
        <p>Email: {this.state.email}</p>
        <p>Domain: {this.state.domain}</p>
      </div>
    );
  }
}
getDerivedStateFromProps is static, so it doesn’t have access to this. It’s a pure function based only on props and state.

componentWillReceiveProps()

Deprecated - Called when component receives new props. Use getDerivedStateFromProps() instead.
Called when a component receives new props (src/diff/index.js:177):
if (
  isClassComponent &&
  newType.getDerivedStateFromProps == NULL &&
  newProps !== oldProps &&
  c.componentWillReceiveProps != NULL
) {
  c.componentWillReceiveProps(newProps, componentContext);
}

shouldComponentUpdate()

Called before rendering when new props or state are received. Return false to skip rendering. Implementation (src/diff/index.js:186):
if (
  newVNode._original == oldVNode._original ||
  (!(c._bits & COMPONENT_FORCE) &&
    c.shouldComponentUpdate != NULL &&
    c.shouldComponentUpdate(
      newProps,
      c._nextState,
      componentContext
    ) === false)
) {
  // Skip rendering
  c.props = newProps;
  c.state = c._nextState;
  c._bits &= ~COMPONENT_DIRTY;
  // ...
}

Example usage

import { Component } from 'preact';

class UserCard extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    // Only update if user ID changed
    return this.props.userId !== nextProps.userId;
  }

  render() {
    return (
      <div className="user-card">
        <h3>User #{this.props.userId}</h3>
        <p>{this.props.name}</p>
      </div>
    );
  }
}
This is a powerful performance optimization. Use it to prevent unnecessary renders.

componentWillUpdate()

Deprecated - Called immediately before rendering. Use getSnapshotBeforeUpdate() instead.
Called before rendering with new props or state (src/diff/index.js:223):
if (c.componentWillUpdate != NULL) {
  c.componentWillUpdate(newProps, c._nextState, componentContext);
}

getSnapshotBeforeUpdate()

Called right before DOM updates are committed. Return value is passed to componentDidUpdate(). Implementation (src/diff/index.js:270):
if (
  isClassComponent &&
  oldVNode._component &&
  c.getSnapshotBeforeUpdate != NULL
) {
  snapshot = c.getSnapshotBeforeUpdate(oldProps, oldState);
}

Example usage

import { Component } from 'preact';

class ChatBox extends Component {
  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Capture scroll position before update
    if (prevProps.messages.length < this.props.messages.length) {
      const list = this.listRef;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // Restore scroll position after update
    if (snapshot !== null) {
      const list = this.listRef;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={el => this.listRef = el}>
        {this.props.messages.map(msg => (
          <div key={msg.id}>{msg.text}</div>
        ))}
      </div>
    );
  }
}

componentDidUpdate()

Called immediately after updating occurs. Good for:
  • Operating on the DOM after an update
  • Making network requests based on prop changes
Implementation (src/diff/index.js:227):
if (isClassComponent && c.componentDidUpdate != NULL) {
  c._renderCallbacks.push(() => {
    c.componentDidUpdate(oldProps, oldState, snapshot);
  });
}

Example usage

import { Component } from 'preact';

class UserData extends Component {
  state = { data: null };

  componentDidUpdate(prevProps) {
    // Fetch new data if userId changed
    if (this.props.userId !== prevProps.userId) {
      fetch(`/api/users/${this.props.userId}`)
        .then(res => res.json())
        .then(data => this.setState({ data }));
    }
  }

  render() {
    return <div>{this.state.data?.name}</div>;
  }
}
Always compare props/state before making side effects in componentDidUpdate() to avoid infinite loops.

Unmounting phase

componentWillUnmount()

Called immediately before a component is unmounted and destroyed. Perfect for:
  • Cleaning up timers
  • Canceling network requests
  • Removing event listeners
  • Cleaning up subscriptions
Implementation (src/diff/index.js:695):
if ((r = vnode._component) != NULL) {
  if (r.componentWillUnmount) {
    try {
      r.componentWillUnmount();
    } catch (e) {
      options._catchError(e, parentVNode);
    }
  }

  r._parentDom = NULL;
}

Example usage

import { Component } from 'preact';

class Timer extends Component {
  state = { seconds: 0 };

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

  componentWillUnmount() {
    // Clean up interval
    clearInterval(this.interval);
  }

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

Lifecycle method order

On mount

  1. constructor()
  2. getDerivedStateFromProps()
  3. render()
  4. DOM updates
  5. componentDidMount()

On update

  1. getDerivedStateFromProps()
  2. shouldComponentUpdate()
  3. render()
  4. getSnapshotBeforeUpdate()
  5. DOM updates
  6. componentDidUpdate()

On unmount

  1. componentWillUnmount()
  2. Component removed from DOM

Complete lifecycle example

Here’s a real-world example demonstrating multiple lifecycle methods:
import { Component } from 'preact';

class UserDashboard extends Component {
  constructor(props) {
    super(props);
    this.state = {
      user: null,
      loading: true,
      error: null
    };
  }

  static getDerivedStateFromProps(props, state) {
    // Reset error when userId changes
    if (props.userId !== state.prevUserId) {
      return {
        prevUserId: props.userId,
        error: null
      };
    }
    return null;
  }

  componentDidMount() {
    this.fetchUser();
    // Set up polling
    this.pollInterval = setInterval(() => this.fetchUser(), 30000);
  }

  componentDidUpdate(prevProps) {
    // Fetch new user data if ID changed
    if (this.props.userId !== prevProps.userId) {
      this.fetchUser();
    }
  }

  componentWillUnmount() {
    // Clean up polling
    clearInterval(this.pollInterval);
    // Cancel any pending requests
    if (this.controller) {
      this.controller.abort();
    }
  }

  fetchUser = async () => {
    this.setState({ loading: true });
    this.controller = new AbortController();

    try {
      const response = await fetch(
        `/api/users/${this.props.userId}`,
        { signal: this.controller.signal }
      );
      const user = await response.json();
      this.setState({ user, loading: false, error: null });
    } catch (error) {
      if (error.name !== 'AbortError') {
        this.setState({ error, loading: false });
      }
    }
  };

  shouldComponentUpdate(nextProps, nextState) {
    // Only update if data actually changed
    return (
      this.props.userId !== nextProps.userId ||
      this.state.user !== nextState.user ||
      this.state.loading !== nextState.loading ||
      this.state.error !== nextState.error
    );
  }

  render() {
    const { loading, error, user } = this.state;

    if (loading && !user) return <div>Loading...</div>;
    if (error) return <div>Error: {error.message}</div>;
    if (!user) return <div>No user found</div>;

    return (
      <div className="dashboard">
        <h1>{user.name}</h1>
        <p>Email: {user.email}</p>
        <p>Role: {user.role}</p>
        {loading && <span className="updating">Updating...</span>}
      </div>
    );
  }
}
This example shows:
  • Initializing state in constructor()
  • Deriving state from props with getDerivedStateFromProps()
  • Fetching data in componentDidMount()
  • Updating when props change in componentDidUpdate()
  • Cleaning up in componentWillUnmount()
  • Optimizing with shouldComponentUpdate()

Error boundaries

Error boundaries catch JavaScript errors anywhere in their component tree:
import { Component } from 'preact';

class ErrorBoundary extends Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    // Log error to error reporting service
    console.error('Error caught:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h1>Something went wrong</h1>
          <p>{this.state.error.message}</p>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
<ErrorBoundary>
  <App />
</ErrorBoundary>
Error boundaries only catch errors in child components, not in the error boundary itself.

Build docs developers (and LLMs) love