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 }));
}