Files
awesome-copilot/plugins/react18-upgrade/skills/react18-lifecycle-patterns/references/componentWillMount.md
2026-04-09 06:26:21 +00:00

3.7 KiB

componentWillMount Migration Reference

Case A - Initializes State

The method only calls this.setState() with static or computed values that do not depend on async operations.

Before:

class UserList extends React.Component {
  componentWillMount() {
    this.setState({ items: [], loading: false, page: 1 });
  }
  render() { ... }
}

After - move to constructor:

class UserList extends React.Component {
  constructor(props) {
    super(props);
    this.state = { items: [], loading: false, page: 1 };
  }
  render() { ... }
}

If constructor already exists, merge the state:

class UserList extends React.Component {
  constructor(props) {
    super(props);
    // Existing state merged with componentWillMount state:
    this.state = {
      ...this.existingState,  // whatever was already here
      items: [],
      loading: false,
      page: 1,
    };
  }
}

Case B - Runs a Side Effect

The method fetches data, sets up subscriptions, interacts with external APIs, or touches the DOM.

Before:

class UserDashboard extends React.Component {
  componentWillMount() {
    this.subscription = this.props.eventBus.subscribe(this.handleEvent);
    fetch(`/api/users/${this.props.userId}`)
      .then(r => r.json())
      .then(user => this.setState({ user, loading: false }));
    this.setState({ loading: true });
  }
}

After - move to componentDidMount:

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

  componentDidMount() {
    // All side effects move here - runs after first render
    this.subscription = this.props.eventBus.subscribe(this.handleEvent);
    fetch(`/api/users/${this.props.userId}`)
      .then(r => r.json())
      .then(user => this.setState({ user, loading: false }));
  }

  componentWillUnmount() {
    // Always pair subscriptions with cleanup
    this.subscription?.unsubscribe();
  }
}

Why this is safe: In React 18 concurrent mode, componentWillMount can be called multiple times before mounting. Side effects inside it can fire multiple times. componentDidMount is guaranteed to fire exactly once after mount.


Case C - Derives Initial State from Props

The method reads this.props to compute an initial state value.

Before:

class PriceDisplay extends React.Component {
  componentWillMount() {
    this.setState({
      formattedPrice: `$${this.props.price.toFixed(2)}`,
      isDiscount: this.props.price < this.props.originalPrice,
    });
  }
}

After - constructor with props:

class PriceDisplay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      formattedPrice: `$${props.price.toFixed(2)}`,
      isDiscount: props.price < props.originalPrice,
    };
  }
}

Note: If this initial state needs to UPDATE when props change later, that's a getDerivedStateFromProps case - see componentWillReceiveProps.md Case B.


Multiple Patterns in One Method

If a single componentWillMount does both state init AND side effects:

// Mixed - state init + fetch
componentWillMount() {
  this.setState({ loading: true, items: [] });              // Case A
  fetch('/api/items').then(r => r.json())                   // Case B
    .then(items => this.setState({ items, loading: false }));
}

Split them:

constructor(props) {
  super(props);
  this.state = { loading: true, items: [] }; // Case A → constructor
}

componentDidMount() {
  fetch('/api/items').then(r => r.json())    // Case B → componentDidMount
    .then(items => this.setState({ items, loading: false }));
}