React Core Fundamentals
Before you write a single component, you need to understand why React exists and how it works behind the scenes. This foundation will make everything else â hooks, performance, patterns â click into place.
What Is React and Why Does It Exist?
Browsers display web pages using something called the DOM (Document Object Model). Think of the DOM as a giant tree of HTML elements â every <div>, <button>, and <p> is a node in that tree.
The problem? Directly updating the DOM is slow. Every time you change something, the browser has to recalculate layouts, repaint pixels, and reflow elements. If your app has hundreds of elements updating frequently â like a chat app or a dashboard â the page gets sluggish.
React solves this by acting as a middleman. Instead of you telling the browser exactly what to change, you tell React what the UI should look like, and React figures out the most efficient way to update the real DOM.
function WelcomeMessage({ name }) {
return <h1>Hello, {name}!</h1>;
}You don't write "find the h1, change its text." You just say "the h1 should show this name." React handles the rest.
Remember: React's whole purpose is to make UI updates fast and predictable. You describe the what, React handles the how.
What Is the Virtual DOM?
Think of it like this: imagine you're an architect. You don't demolish an entire building every time a client wants to change one room. Instead, you update the blueprint first, compare the new blueprint with the old one, and then send workers to change only the rooms that are different.
That's exactly how the Virtual DOM works:
- Your component renders â React creates a lightweight JavaScript copy of the DOM (the "blueprint")
- Something changes (a button click, new data) â React creates a new blueprint
- React compares the old blueprint with the new one (this is called diffing)
- React updates only what changed in the real DOM
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Add One</button>
</div>
);
}When you click the button, React doesn't rebuild the entire <div>. It sees that only the text inside <p> changed â from "Count: 0" to "Count: 1" â and updates just that one piece.
Think of it like: You have a Google Doc and a printed copy. You edit the Google Doc (Virtual DOM), then compare it with the printed version (real DOM), and only reprint the pages that changed.
What Is Reconciliation?
Reconciliation is React's diffing algorithm â the process of comparing the old Virtual DOM tree with the new one and figuring out the minimum number of changes needed.
Here's how React's diffing works, simplified:
- Different element types? Tear down the old tree and build a new one. If a
<div>becomes a<span>, React starts fresh. - Same element type? Keep the DOM node, update only the changed attributes.
- Lists of elements? This is where keys come in (more on that next).
// Before
<div className="old">
<p>Hello</p>
</div>
// After
<div className="new">
<p>Hello</p>
</div>React sees the <div> is the same element type. It doesn't destroy it â it just updates the className from "old" to "new". The <p> inside is identical, so React skips it entirely.
Think of it like: A spot-the-difference puzzle. React looks at two pictures (old UI vs. new UI) and circles only the differences. Then it fixes just those spots.
Fiber Architecture
In the early days, React's reconciliation was synchronous â it would start comparing trees and couldn't stop until it was done. For large apps, this meant the browser could freeze during big updates.
React Fiber (introduced in React 16) was a complete rewrite of the reconciliation engine. The key upgrade: work can be paused and resumed.
Think of it like this: the old system was a chef who had to cook an entire 10-course meal before serving anything. Fiber is a chef who can pause between courses to take urgent orders (like handling a user click).
Fiber enables:
- Prioritized updates â a user typing in an input is more urgent than a background data fetch
- Interruptible rendering â React can pause work to handle high-priority tasks
- Concurrent features â like
useTransitionandSuspense
import { useTransition } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
function handleChange(e) {
setQuery(e.target.value);
startTransition(() => {
setResults(filterLargeDataset(e.target.value));
});
}
return (
<div>
<input value={query} onChange={handleChange} />
{isPending ? <p>Searching...</p> : <ResultList results={results} />}
</div>
);
}The input stays responsive because the typing update is high priority, while filtering results is wrapped in startTransition â a lower priority that Fiber can interrupt.
Remember: You don't interact with Fiber directly. It's the engine under the hood. But understanding that React can pause and prioritize work explains why modern features like
useTransitionandSuspenseexist.
What Are Keys and Why Do They Matter?
When React renders a list, it needs a way to tell which items are which. Keys are unique identifiers you give to list items so React can track them efficiently.
Without keys, React has no idea which item moved, which was added, and which was removed. It would have to destroy and rebuild the entire list.
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}key={todo.id} tells React: "This <li> belongs to this specific todo. If the list reorders, move the DOM node â don't destroy and recreate it."
What Happens If You Use Index as Key?
This is one of the most common beginner mistakes:
// Seems harmless...
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li>
))}Why it breaks: If you add an item at the beginning of the list, every item's index shifts. React thinks item 0 changed, item 1 changed, item 2 changed â it re-renders the entire list instead of just inserting one new item.
Worse â if your list items have state (like an input field), the state gets mixed up between items.
// Bad: Using index as key with stateful items
function BadTodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Buy groceries' },
{ id: 2, text: 'Walk the dog' },
]);
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>
<input defaultValue={todo.text} />
</li>
))}
</ul>
);
}If you add a new todo at the top, the input values stay in their old positions while the todo data shifts â the wrong text appears in the wrong input.
Common Mistake: Using
key={index}is fine for static lists that never reorder, add, or remove items. But for anything dynamic, always use a unique, stable ID likekey={item.id}.
Interview Q&A:
Q: When is it safe to use array index as a key?
Only when ALL of these are true: the list is static (no additions, deletions, or reordering), and items have no local state or uncontrolled inputs. If any of those conditions might change in the future, use a stable unique ID.
Controlled vs. Uncontrolled Components
When working with forms, you'll encounter two approaches to handling input values.
Controlled Components
React controls the input's value. The input always reflects the state, and every change goes through a handler.
Think of it like a puppet on strings â React is the puppeteer, and the input does exactly what React tells it to.
function ControlledForm() {
const [name, setName] = useState('');
function handleSubmit(e) {
e.preventDefault();
console.log('Submitted:', name);
}
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
);
}Pros: You have full control. You can validate on every keystroke, format the value, conditionally disable the submit button, etc.
Uncontrolled Components
The DOM itself holds the input's value. You read it when you need it (usually on submit) using a ref.
Think of it like a suggestion box â people write whatever they want, and you only read it when you open the box.
function UncontrolledForm() {
const nameRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
console.log('Submitted:', nameRef.current.value);
}
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} defaultValue="" />
<button type="submit">Submit</button>
</form>
);
}Pros: Less code, no re-render on every keystroke. Useful for simple forms or when integrating with non-React libraries.
When to Use Which?
| Scenario | Controlled | Uncontrolled |
|---|---|---|
| Real-time validation | Yes | No |
| Conditional submit button | Yes | No |
| Format input as user types | Yes | No |
| Simple form, read on submit | Either | Yes |
| Integrating with non-React code | No | Yes |
Remember: Most of the time, controlled components are the right choice. They give you predictable behavior and full control over the input. Use uncontrolled components only when you have a good reason (performance-sensitive forms with many fields, third-party library integration).
Interview Q&A:
Q: What's the difference between controlled and uncontrolled components?
In a controlled component, React owns the value â it's stored in state and updated via onChange. In an uncontrolled component, the DOM owns the value â you read it with a ref when needed. Controlled components give you more control (validation, formatting), while uncontrolled components can be simpler for basic forms.
Chapter Summary
| Concept | One-Liner |
|---|---|
| React | A library that makes UI updates fast by being a smart middleman between you and the DOM |
| Virtual DOM | A lightweight copy of the real DOM â React's "blueprint" |
| Reconciliation | React's spot-the-difference algorithm that finds the minimum changes needed |
| Fiber | The engine that lets React pause, prioritize, and resume rendering work |
| Keys | Unique IDs that help React track list items efficiently |
| Controlled | React owns the input value (puppet on strings) |
| Uncontrolled | The DOM owns the input value (suggestion box) |