Understand React component lifecycle phases and map class methods to hooks.
function Component() {
// Mount: run once
useEffect(() => {
console.log("Mounted");
}, []);
// Unmount: cleanup
useEffect(() => {
return () => console.log("Unmounting");
}, []);
}// In development with Strict Mode, lifecycle methods run twice
// This is intentional — it proves teardown and setup are symmetric// Changing props or state updates the component
// — it does NOT unmount and remountkey prop forces React to unmount the old instance and mount a fresh one.// Changing the key remounts the component from scratch,
// resetting all of its internal state
<UserPanel key={userId} />class Profile extends React.Component {
componentDidMount() {
console.log("Mounted, safe to fetch or measure DOM");
}
}componentDidUpdate(prevProps, prevState) {
if (prevProps.userId !== this.props.userId) {
this.loadUser(this.props.userId);
}
}componentWillUnmount() {
this.subscription.unsubscribe();
}shouldComponentUpdate(nextProps) {
// Skip re-render if the displayed value hasn't changed
return nextProps.value !== this.props.value;
}static getDerivedStateFromProps(props, state) {
if (props.id !== state.prevId) {
return { selected: null, prevId: props.id };
}
return null;
}// Class: componentDidMount() { ... }
useEffect(() => {
// runs once, after mount
}, []);// Class: componentDidUpdate(prevProps) { if (prevProps.id !== this.props.id) ... }
useEffect(() => {
// runs after mount AND after every id change
}, [id]);// Class: componentWillUnmount() { this.sub.unsubscribe(); }
useEffect(() => {
const sub = subscribe();
return () => sub.unsubscribe();
}, []);// Class: extends React.PureComponent, or custom shouldComponentUpdate
const Row = React.memo((props) => {
return <tr>{props.label}</tr>;
});// Class: static getDerivedStateFromProps synced state.prevId to props.id
const [prevId, setPrevId] = useState(id);
if (id !== prevId) {
setPrevId(id);
setSelected(null); // adjust state during render, not in an effect
}// Parent state change re-renders Child even if childProp never changes
function Parent() {
const [count, setCount] = useState(0);
return <Child childProp="static" />;
}const Child = React.memo(({ childProp }) => {
return <div>{childProp}</div>;
}); // re-renders only when childProp's reference changesclass Row extends React.PureComponent {
// Automatically skips render if props/state are shallowly equal
render() { return <tr>{this.props.label}</tr>; }
}// Using array index as key can cause React to reuse the wrong instance
// when items are reordered — prefer a stable id
items.map((item) => <Row key={item.id} item={item} />)// Without useCallback, onSave is a new function every render,
// which breaks React.memo on Child
const onSave = useCallback(() => save(id), [id]);componentDidMount() {
// Causes a second render right after the first — avoid when possible
this.setState({ ready: true });
}componentWillUnmount() {
// Warning: Can't perform a React state update on an unmounted component
this.setState({ closed: true }); // remove this call instead
}// Mount order: Child.componentDidMount, then Parent.componentDidMount
// Unmount order: Parent.componentWillUnmount, then Child.componentWillUnmountshouldComponentUpdate(nextProps) {
// Bug: compares the array reference, not its contents
return nextProps.items !== this.props.items;
}static getDerivedStateFromProps(props) {
// Wrong: side effects don't belong here, this method must be pure
// fetch(props.url) — move data fetching to componentDidMount/useEffect instead
return null;
}componentDidMount is a class lifecycle method that fires once after the initial render, before the browser paints in some cases. useEffect with an empty dependency array is the closest functional equivalent, but it always runs after paint, and it folds in cleanup logic that previously required a separate componentWillUnmount method.
React re-renders a component whenever its parent re-renders, regardless of whether the props look the same, unless you opt out with React.memo or shouldComponentUpdate. Object and array props that are recreated on every parent render also count as changed, because React compares references, not deep equality.
React calls componentWillUnmount (or runs effect cleanup functions) synchronously before detaching the component from the DOM. This is the only guaranteed point to cancel timers, close connections, and unsubscribe — after this, the component instance is discarded and any further state updates are ignored or warned about.
Yes — wrap function components in React.memo or extend PureComponent for class components, both of which shallow-compare props and skip rendering when nothing changed. For state-driven re-renders inside the same component, shouldComponentUpdate or splitting state into smaller, more targeted pieces avoids unnecessary work.
React 18 Strict Mode intentionally mounts, unmounts, and remounts components in development to surface lifecycle code that isn't resilient to being torn down and set up again. This double-invoke only happens in development with Strict Mode enabled — production builds run each lifecycle step once.