How did React 18 change the lifecycle?
1. The main idea of React 18 - Concurrent Rendering
Before React 18:
- React rendered a component synchronously: once rendering started, it ran to completion - it couldn't be paused, cancelled, or merged with other updates.
- All lifecycles were called in a strictly predictable order (mount -> commit -> effects).
With React 18:
- Rendering became asynchronous and interruptible (via Fiber concurrent mode).
- React can now:
- pause rendering,
- roll back incomplete changes,
- re-run effects,
- merge several state updates into one "session".
This directly affects calls to the lifecycle and hooks, since effects can now run more than once in dev mode, and "mounting" can be simulated multiple times.
2. Behavior in Strict Mode (React 18)
The change most noticeable to developers:
React 18 now calls
useEffect,useLayoutEffect,componentDidMount,componentWillUnmounttwice (in DEV mode), to help find "impure effects".
This is called "Strict Mode double invocation" and happens only in dev,
to verify that your effects and cleanups (cleanup) are deterministic and don't depend on call order.
Example:
useEffect(() => {
console.log('effect');
return () => console.log('cleanup');
}, []);In React 18 (in StrictMode) you will see:
effect
cleanup
effectWhy: React simulates unmounting and remounting to verify that your effect correctly cleans up its resources (timers, listeners, subscriptions).
This isn't a bug, it's part of the new "concurrent safety check".
3. Changes to lifecycle phases under the hood
The lifecycle is now understood within two Fiber phases:
| Phase | Purpose | Methods/hooks |
|---|---|---|
| Render Phase | React computes what needs to be updated (can be paused) | render(), useMemo, useCallback, shouldComponentUpdate |
| Commit Phase | React applies changes to the DOM (always synchronous) | componentDidMount, componentDidUpdate, useLayoutEffect, useEffect |
In React 18 the Render Phase can:
- run multiple times (React can "prepare" different versions of the UI and then choose one);
- not reach commit, if the render is cancelled;
- run in the background (background rendering).
4. What specifically changed in lifecycle behavior
componentWillMount, componentWillUpdate, componentWillReceiveProps
Officially deprecated even earlier (React 16) and fully incompatible with concurrent mode - React 18 doesn't call them at all in concurrent rendering. Their safe replacements are:
getDerivedStateFromPropsgetSnapshotBeforeUpdatecomponentDidUpdate
componentDidMount / useEffect / useLayoutEffect
- Still called in the commit phase, but React can call cleanup -> effect again when switching between renders of different priority.
- In dev mode (
StrictMode) they run twice on mount (see above). - In concurrent mode React can cancel a render before commit, and in that case the effects won't be called at all (since the DOM didn't change).
componentDidUpdate
- Called only for components that were actually committed. If the render is cancelled (for example, on a transition to a new state), the method isn't called.
componentWillUnmount / cleanup in useEffect
- Now React guarantees it calls cleanup:
- on unmount,
- before a new effect,
- on a cancelled render (in some dev-mode cases).
- This makes effect logic more predictable and safe.
shouldComponentUpdate
- Its behavior hasn't changed directly, but React can now interrupt or defer rendering, so the check may run several times before commit.
5. New React 18 features affecting the "lifecycle"
| New feature | What it does | How it affects things |
|---|---|---|
| Concurrent Rendering | Asynchronous, priority-based rendering | Methods can be called more than once, effects can be deferred |
| Automatic Batching | React now groups all state updates, even inside promises | Fewer "extra" re-renders, different useEffect behavior |
Transitions (startTransition) | Lets you separate "urgent" and "non-urgent" updates | The lifecycle of low-priority updates can be called later |
| useId, useSyncExternalStore, useInsertionEffect | New hooks for correct behavior in concurrent mode | Optimize access to the DOM, styles, and external data |
6. What changed for useEffect
| Behavior | React 17 | React 18 |
|---|---|---|
| The effect runs once in dev | Yes | No - twice (Strict Mode) |
| Cleanup runs only on unmount | Yes | No - also before the effect runs again |
| The effect can be deferred | No | Yes (due to concurrent rendering) |
| A "cancelled" effect is possible (if commit didn't happen) | No | Yes |
7. The main thing to remember
- React 18 doesn't "break" the lifecycle, it makes it more precise, asynchronous, and safe.
- In production, effects are still called once - "twice" only happens in dev.
- It's important to write pure effects - with no side effects in the component body and no dependency on the number of calls.
Short summary table
| What changed | React < 18 | React 18 |
|---|---|---|
| Rendering | Synchronous | Asynchronous / priority-based |
| Lifecycle | Single-pass | Can be paused / repeated |
Effects (useEffect) | Single call | Double call in StrictMode (dev) |
Cleanup (cleanup) | Only on unmount | Also on remounting |
Old methods (componentWill*) | Work (legacy mode) | Incompatible |
| State batching | Only inside events | Automatic everywhere |
| New hooks | - | useId, useSyncExternalStore, useInsertionEffect |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.