Loading...
Loading...
By continuing to use the platform, you accept the terms of the Privacy Policy and the use of cookies.
Reconciliation is the algorithm React uses to compare two element trees and determine which parts of the real DOM need to be updated.
When a component's state or props change, React creates a new element tree and compares it with the previous one to make minimal changes to the DOM.
Updating the DOM is an expensive operation. React optimizes this process:
React uses a heuristic O(n) algorithm instead of O(n³). It's based on two assumptions:
When root elements have different types, React removes the old tree and builds a new one from scratch.
// Old tree
<div>
<Counter />
</div>
// New tree
<span>
<Counter />
</span>
React:
<div> and all childrenCounter (calls componentWillUnmount)<span>Counter (calls componentDidMount)When types match, React compares attributes and updates only changed ones:
// Old
<div className="before" title="old" />
// New
<div className="after" title="old" />
React will update only className, title stays unchanged.
// Old
<div style={{color: 'red', fontWeight: 'bold'}} />
// New
<div style={{color: 'blue', fontWeight: 'bold'}} />
React updates only color.
When a component updates, the instance remains the same, so state is preserved:
<Counter count={1} />
// After update
<Counter count={2} />
React:
componentWillReceiveProps() and componentWillUpdate()render()React recursively processes children. Let's look at an example:
// Old
<ul>
<li>first</li>
<li>second</li>
</ul>
// New
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
React:
<li> — identical, does nothing<li> — identical, does nothing<li>Efficient!
// Old
<ul>
<li>first</li>
<li>second</li>
</ul>
// New
<ul>
<li>zero</li>
<li>first</li>
<li>second</li>
</ul>
React:
<li> — "first" ≠ "zero", updates<li> — "second" ≠ "first", updates<li>Inefficient! React didn't understand that elements just shifted.
key helps React understand which elements changed, were added, or removed.
// Old
<ul>
<li key="first">first</li>
<li key="second">second</li>
</ul>
// New
<ul>
<li key="zero">zero</li>
<li key="first">first</li>
<li key="second">second</li>
</ul>
React:
Efficient!
Keys need to be unique only among sibling elements:
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map(post =>
<li key={post.id}>{post.title}</li>
)}
</ul>
);
const content = props.posts.map(post =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}
Same keys in different arrays is fine.
Keys should be stable and predictable. Don't use random values:
// Bad
{items.map(item => <Item key={Math.random()} data={item} />)}
// Bad (state will be lost on reordering)
{items.map((item, index) => <Item key={index} data={item} />)}
// Good
{items.map(item => <Item key={item.id} data={item} />)}
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Buy milk', done: false },
{ id: 2, text: 'Clean room', done: true }
]);
// Bad
return (
<ul>
{todos.map((todo, index) => (
<TodoItem key={index} todo={todo} />
))}
</ul>
);
}
Problem: when removing the first element, indices shift, and React thinks the content changed, not the order.
// Good
return (
<ul>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
You can optimize reconciliation by telling React when a component DOESN'T need re-rendering:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Render only if id changed
return this.props.id !== nextProps.id;
}
render() {
return <div>{this.props.data}</div>;
}
}
Automatically performs shallow comparison of props and state:
class MyComponent extends React.PureComponent {
render() {
return <div>{this.props.data}</div>;
}
}
For functional components:
const MyComponent = React.memo(function MyComponent(props) {
return <div>{props.data}</div>;
});
// With custom comparison
const MyComponent = React.memo(
function MyComponent(props) {
return <div>{props.data}</div>;
},
(prevProps, nextProps) => {
return prevProps.id === nextProps.id;
}
);
function UserList({ users, filter }) {
const filteredUsers = users.filter(user =>
user.name.includes(filter)
);
return (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>
{user.name}
<input type="text" />
</li>
))}
</ul>
);
}
If using index instead of user.id, input values will get mixed up when changing the filter because React won't understand these are different users.
function App() {
const [tab, setTab] = useState('profile');
return (
<div>
{tab === 'profile' && <Profile />}
{tab === 'settings' && <Settings />}
</div>
);
}
When switching tabs, React fully unmounts one component and mounts another because they're different types.
To preserve state, use display: none:
function App() {
const [tab, setTab] = useState('profile');
return (
<div>
<div style={{ display: tab === 'profile' ? 'block' : 'none' }}>
<Profile />
</div>
<div style={{ display: tab === 'settings' ? 'block' : 'none' }}>
<Settings />
</div>
</div>
);
}
function Component({ isLoading }) {
if (isLoading) {
return <div>Loading...</div>;
}
return <span>Data</span>;
}
When isLoading changes, React unmounts the entire component because div ≠ span.
Solution: use the same type:
function Component({ isLoading }) {
return <div>{isLoading ? 'Loading...' : 'Data'}</div>;
}
// Bad
function List({ items }) {
return (
<ul>
{items.map(item => <li>{item.name}</li>)}
</ul>
);
}
React will use ordinal numbers by default, leading to problems when order changes.
// Bad
function List({ items }) {
return (
<ul>
{items.map(item => (
<li key={`${item.name}-${Date.now()}`}>
{item.name}
</li>
))}
</ul>
);
}
Key changes on every render, React will recreate elements.
Reconciliation:
In interviews:
Important to be able to: