Reactbeginner

React Fundamentals — Everything You Need to Know

Master React fundamentals: components, JSX, props, state, rendering lifecycle, re-rendering triggers, Fragments, and more. The complete beginner-to-confident guide with 20 code examples.

22 min read·Published Mar 2, 2026
reactcomponentsjsxpropsstatelifecyclerendering

What Is React?

React is a JavaScript library for building user interfaces. Created by Meta (formerly Facebook) and open-sourced in 2013, it has become the most widely adopted UI library in the frontend ecosystem. React lets you compose complex UIs from small, isolated pieces of code called components.

React's core ideas:

  1. Declarative — describe what the UI should look like, React handles the DOM updates
  2. Component-Based — build encapsulated components that manage their own state
  3. Unidirectional Data Flow — data flows down from parent to child via props
  4. Virtual DOM — React maintains an in-memory representation of the real DOM and syncs them efficiently

Before React, developers directly manipulated the DOM using jQuery or vanilla JavaScript. This was error-prone and hard to scale. React introduced a mental model where you describe the desired UI state and let the library figure out the minimal set of DOM changes needed.

Traditional DOM manipulation:
  [User Action] --> [Find Element] --> [Modify Element] --> [Hope nothing breaks]

React's declarative model:
  [State Change] --> [Describe UI] --> [React diffs VDOM] --> [Minimal DOM update]

Components

Components are the building blocks of React applications. Every React app is a tree of components, starting from a root component down to the smallest UI elements.

Function Components

Function components are the modern standard. They are plain JavaScript functions that accept props and return JSX.

// Function declaration
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// Arrow function
const Greeting = ({ name }) => {
  return <h1>Hello, {name}!</h1>;
};

// Implicit return (single expression)
const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;

Class Components (Legacy)

Class components were the original way to write components with state and lifecycle methods. You will still see them in older codebases, but new code should use function components with hooks.

import { Component } from 'react';

class Greeting extends Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

Function vs Class Components

FeatureFunction ComponentsClass Components
SyntaxPlain functionES6 class extending Component
StateuseState hookthis.state + this.setState
LifecycleuseEffect hookcomponentDidMount, etc.
this keywordNot neededRequired (bindings can be tricky)
Code lengthShorterMore boilerplate
PerformanceSlightly better (no class overhead)Slightly heavier
Current statusRecommendedLegacy (still supported)

Component Rules

  1. Component names must start with a capital letter — lowercase names are treated as HTML elements
  2. Components must return JSX (or null)
  3. Components should be pure — same input, same output during render
  4. Side effects go in useEffect, not in the render body
  5. Components should have a single responsibility — do one thing well

Composing Components

Real applications are built by composing components into a tree. Each component manages its own piece of the UI.

function App() {
  return (
    <div>
      <Header />
      <main>
        <Sidebar />
        <Content>
          <ArticleList />
        </Content>
      </main>
      <Footer />
    </div>
  );
}

function Header() {
  return (
    <header>
      <Logo />
      <Navigation />
      <SearchBar />
    </header>
  );
}
Component tree:

        App
       / | \
  Header Main Footer
   /|\     / \
Logo Nav  Sidebar Content
  Search          |
              ArticleList

JSX In Depth

JSX is a syntax extension that looks like HTML but compiles to JavaScript function calls. It is not a template language — it has the full power of JavaScript.

// JSX
const element = <h1 className="title">Hello</h1>;

// Compiles to
const element = React.createElement('h1', { className: 'title' }, 'Hello');

With React 17+, a new JSX transform was introduced so you no longer need to import React at the top of every file. The compiler handles the transformation automatically.

JSX Rules

  • Return a single root element (or use Fragments <>...</>)
  • Close all tags: <img />, <br />
  • Use className instead of class
  • Use htmlFor instead of for
  • Use camelCase for event handlers and DOM properties: onClick, onChange, tabIndex
  • JavaScript expressions go inside {curly braces}

Embedding Expressions

Any valid JavaScript expression can appear inside curly braces in JSX.

function UserGreeting({ user }) {
  const formattedDate = new Date().toLocaleDateString();

  return (
    <div>
      <h1>Welcome, {user.firstName + ' ' + user.lastName}</h1>
      <p>Today is {formattedDate}</p>
      <p>You have {user.messages.length} messages</p>
      <p>Status: {user.isActive ? 'Active' : 'Inactive'}</p>
      <img
        src={user.avatar}
        alt={`Avatar of ${user.firstName}`}
        style={{ borderRadius: '50%', width: 64, height: 64 }}
      />
    </div>
  );
}

JSX Gotchas

// 1. Boolean, null, undefined render nothing
<div>{true}</div>    // renders: <div></div>
<div>{null}</div>    // renders: <div></div>
<div>{undefined}</div> // renders: <div></div>

// 2. Zero IS rendered (common bug with && operator)
<div>{0 && <Child />}</div>  // renders: <div>0</div>  (Bug!)
<div>{items.length > 0 && <Child />}</div>  // correct

// 3. Objects cannot be rendered directly
<div>{{ name: 'Alice' }}</div>  // Error: Objects are not valid as React child

// 4. Arrays are flattened
<div>{[1, 2, 3]}</div>  // renders: <div>123</div>

Inline Styles

JSX inline styles use a JavaScript object with camelCased property names, not a CSS string.

// Wrong
<div style="color: red; font-size: 16px">Hello</div>

// Correct
<div style={{ color: 'red', fontSize: '16px' }}>Hello</div>
<div style={{ color: 'red', fontSize: 16 }}>Hello</div>  // numbers default to px

Props

Props (short for "properties") are how you pass data from parent to child components. They are read-only — a child component should never modify the props it receives.

// Parent passes props
<UserProfile name="Alice" age={30} isAdmin={true} />

// Child receives props
function UserProfile({ name, age, isAdmin }) {
  return (
    <div>
      <p>{name}, {age}</p>
      {isAdmin && <span className="badge">Admin</span>}
    </div>
  );
}

Destructuring Props

There are multiple ways to access props:

// 1. Destructure in parameter (recommended)
function Button({ variant, size, children }) {
  return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
}

// 2. Access from props object
function Button(props) {
  return (
    <button className={`btn-${props.variant} btn-${props.size}`}>
      {props.children}
    </button>
  );
}

// 3. Destructure with rest (spread remaining props)
function Button({ variant, size, children, ...rest }) {
  return (
    <button className={`btn-${variant} btn-${size}`} {...rest}>
      {children}
    </button>
  );
}

// Usage: <Button variant="primary" size="lg" disabled onClick={fn}>Click</Button>

Default Props

function Button({ variant = 'primary', size = 'md', children }) {
  return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
}

// All of these work:
<Button>Click</Button>                    // variant='primary', size='md'
<Button variant="danger">Delete</Button>  // variant='danger', size='md'

Children Prop

The special children prop contains whatever you put between the component's opening and closing tags.

function Card({ title, children }) {
  return (
    <div className="card">
      <div className="card-header">{title}</div>
      <div className="card-body">{children}</div>
    </div>
  );
}

// Usage
<Card title="Settings">
  <p>Notification preferences</p>
  <Toggle label="Email" />
  <Toggle label="Push" />
</Card>

Passing Functions as Props (Callbacks)

Props are not limited to data. You can pass functions to let children communicate back to parents.

function Parent() {
  const [items, setItems] = useState([]);

  const handleAddItem = (item) => {
    setItems(prev => [...prev, item]);
  };

  return (
    <div>
      <ItemList items={items} />
      <AddItemForm onAdd={handleAddItem} />
    </div>
  );
}

function AddItemForm({ onAdd }) {
  const [text, setText] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      onAdd({ id: Date.now(), text });
      setText('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button type="submit">Add</button>
    </form>
  );
}
Data flow:

  Parent (owns state)
    |           ^
    | props     | callback
    v           |
  Child (receives & calls back)

Props vs State Comparison

PropsState
Who owns it?ParentThe component itself
Can change?No (read-only for receiver)Yes (via setter function)
Triggers re-render?Yes, when parent passes new valueYes, when setter is called
DirectionParent -> ChildLocal to component
Use forConfiguring child behaviorManaging changing data

State

State is data that changes over time. When state changes, React re-renders the component (and its children) to reflect the new state.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

State Update Rules

  1. Never mutate state directly — always use the setter function
  2. State updates are batched — multiple setState calls in the same event handler trigger only one re-render
  3. State updates may be asynchronous — you cannot rely on state having the new value immediately after calling setState
  4. Use functional updates when new state depends on previous state
// Wrong — mutating state directly
const [user, setUser] = useState({ name: 'Alice', age: 30 });
user.age = 31;  // NEVER do this

// Right — create a new object
setUser({ ...user, age: 31 });
// Or with functional update:
setUser(prev => ({ ...prev, age: prev.age + 1 }));

State Batching

React batches state updates that happen inside event handlers. This means multiple setState calls result in a single re-render.

function BatchExample() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  const handleClick = () => {
    setCount(c => c + 1);  // Does NOT re-render yet
    setFlag(f => !f);       // Does NOT re-render yet
    // React re-renders ONCE here, after the handler finishes
  };

  console.log('Render');  // Logs once per click, not twice

  return <button onClick={handleClick}>Update ({count})</button>;
}

Since React 18, batching also applies to promises, setTimeout, and native event handlers — not just React event handlers.

Component Lifecycle

Even though function components don't have explicit lifecycle methods like class components, they still follow a lifecycle. Understanding this lifecycle is essential for writing correct React code.

The Three Phases

MOUNTING          UPDATING                    UNMOUNTING
(component        (state/props change)         (component removed
 appears)                                       from DOM)
    |                    |                          |
    v                    v                          v
 [Render]  --->  [Re-render on change]  --->  [Cleanup]
 [DOM update]    [DOM update]                 [Remove from DOM]
 [Effects run]   [Cleanup prev effects]
                 [Effects run]

1. Mount Phase — The component is created and inserted into the DOM for the first time.

function MountExample() {
  const [data, setData] = useState(null);

  // This effect runs AFTER the first render (mount)
  useEffect(() => {
    console.log('Component mounted');
    fetchData().then(setData);
  }, []);  // Empty dependency array = run only on mount

  return <div>{data ? data.name : 'Loading...'}</div>;
}

2. Update Phase — The component re-renders because state or props changed.

function UpdateExample({ userId }) {
  const [user, setUser] = useState(null);

  // Runs on mount AND whenever userId changes
  useEffect(() => {
    console.log('Fetching user:', userId);
    fetchUser(userId).then(setUser);
  }, [userId]);  // Re-runs when userId changes

  return <div>{user?.name}</div>;
}

3. Unmount Phase — The component is removed from the DOM. Cleanup functions run.

function UnmountExample() {
  useEffect(() => {
    const interval = setInterval(() => {
      console.log('tick');
    }, 1000);

    // This cleanup function runs when component unmounts
    return () => {
      console.log('Component unmounting, clearing interval');
      clearInterval(interval);
    };
  }, []);

  return <div>Timer running...</div>;
}

Lifecycle Comparison: Class vs Function

Class ComponentFunction Component (Hooks)
constructor()useState(initialValue)
componentDidMount()useEffect(() => {}, [])
componentDidUpdate()useEffect(() => {}, [deps])
componentWillUnmount()useEffect(() => { return () => cleanup }, [])
shouldComponentUpdate()React.memo()
getDerivedStateFromProps()Update state during render (rare)

Re-rendering: When and Why

Understanding when React re-renders is critical for performance and correctness.

What Triggers a Re-render?

  1. State change — calling setState triggers a re-render of that component
  2. Parent re-renders — when a parent re-renders, all its children re-render by default
  3. Context change — when a context value changes, all consumers re-render
  4. Force update — calling forceUpdate() (class) or using a workaround (rare)

What Does NOT Trigger a Re-render?

  1. Ref changes — updating useRef().current does not trigger a re-render
  2. Variable changes — regular variables reset on every render; changing them has no effect
  3. Same state value — if you call setState with the same value (by reference for objects), React bails out
function RenderDemo() {
  const [count, setCount] = useState(0);
  const renderCount = useRef(0);

  renderCount.current += 1;

  return (
    <div>
      <p>Count: {count}</p>
      <p>Renders: {renderCount.current}</p>
      <button onClick={() => setCount(c => c + 1)}>
        Increment (causes re-render)
      </button>
      <button onClick={() => setCount(count)}>
        Set same value (skips re-render)
      </button>
    </div>
  );
}

Re-render Flow

setState called
      |
      v
Is new state === old state?
      |
   Yes |  No
    |     |
    v     v
  Bail  Schedule re-render
  out         |
              v
        Call component function
              |
              v
        Generate new JSX (virtual DOM)
              |
              v
        Diff new VDOM vs old VDOM
              |
              v
        Apply minimal DOM changes
              |
              v
        Run useEffect cleanups (if deps changed)
              |
              v
        Run useEffect callbacks (if deps changed)

Parent Re-render Cascades

When a parent re-renders, all children re-render by default, even if their props haven't changed. This is a common source of performance issues.

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      {/* ExpensiveChild re-renders every time Parent re-renders,
          even though it receives no props from count */}
      <ExpensiveChild />
    </div>
  );
}

// Fix: wrap with React.memo to skip re-render if props haven't changed
const ExpensiveChild = React.memo(function ExpensiveChild() {
  console.log('ExpensiveChild rendered');
  return <div>I am expensive to render</div>;
});

Fragments

Fragments let you group multiple elements without adding an extra DOM node. This is essential because JSX requires a single root element.

The Problem

// This won't work — multiple root elements
function Columns() {
  return (
    <td>Hello</td>
    <td>World</td>
  );
}

Solution 1: Fragment Syntax

import { Fragment } from 'react';

function Columns() {
  return (
    <Fragment>
      <td>Hello</td>
      <td>World</td>
    </Fragment>
  );
}

Solution 2: Short Syntax

function Columns() {
  return (
    <>
      <td>Hello</td>
      <td>World</td>
    </>
  );
}

When You Need Fragment Over <>

The only difference: <Fragment> supports the key attribute. The short syntax <> does not.

function Glossary({ items }) {
  return (
    <dl>
      {items.map(item => (
        // You need Fragment here because you need the key prop
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

Why Fragments Matter

Without Fragment (adds unnecessary <div>):

<table>
  <tr>
    <div>       <-- Invalid HTML! <div> inside <tr>
      <td>A</td>
      <td>B</td>
    </div>
  </tr>
</table>

With Fragment (clean DOM):

<table>
  <tr>
    <td>A</td>  <-- Direct children, valid HTML
    <td>B</td>
  </tr>
</table>

Conditional Rendering

React doesn't have a special template syntax for conditionals. You use standard JavaScript operators.

Ternary Operator

Best when you need to render one thing or another.

function StatusBadge({ isOnline }) {
  return (
    <span className={isOnline ? 'badge-green' : 'badge-gray'}>
      {isOnline ? 'Online' : 'Offline'}
    </span>
  );
}

Logical AND (&&)

Best when you want to render something or nothing.

function Notification({ message, isUrgent }) {
  return (
    <div>
      {message && <p className="notification">{message}</p>}
      {isUrgent && <span className="urgent-icon">!</span>}
    </div>
  );
}

Watch out for falsy values that render:

// Bug: renders "0" on screen when items is empty
{items.length && <ItemList items={items} />}

// Fix: use explicit boolean
{items.length > 0 && <ItemList items={items} />}

Early Return

Best when a condition should prevent the entire component from rendering.

function Dashboard({ user }) {
  if (!user) {
    return <p>Please log in.</p>;
  }

  if (user.isBanned) {
    return <p>Account suspended.</p>;
  }

  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      <DashboardContent />
    </div>
  );
}

Variable Assignment

Best when the logic is complex and you want to keep JSX clean.

function PricingCard({ plan }) {
  let badge;
  if (plan === 'free') {
    badge = <span className="badge-gray">Free</span>;
  } else if (plan === 'pro') {
    badge = <span className="badge-blue">Pro</span>;
  } else {
    badge = <span className="badge-gold">Enterprise</span>;
  }

  return (
    <div className="card">
      {badge}
      <h2>{plan} Plan</h2>
    </div>
  );
}

Rendering Lists

Use .map() to render arrays. Always provide a unique key prop.

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id} className={todo.done ? 'completed' : ''}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

Key Rules

  • Keys must be unique among siblings (not globally unique)
  • Keys should be stable — use IDs from your data, not random values
  • Don't use array index as key if the list can be reordered, filtered, or items can be added/removed in the middle
  • Keys are not passed as props to the component — if you need the ID inside the child, pass it separately

Why Keys Matter

Keys help React identify which items changed, were added, or removed. Without stable keys, React may re-use DOM elements incorrectly, causing bugs.

// Bad — index as key breaks when items reorder
{items.map((item, index) => (
  <ListItem key={index} item={item} />
))}

// Good — stable unique ID
{items.map(item => (
  <ListItem key={item.id} item={item} />
))}

Rendering Nested Lists

function CategoryList({ categories }) {
  return (
    <div>
      {categories.map(category => (
        <section key={category.id}>
          <h2>{category.name}</h2>
          <ul>
            {category.items.map(item => (
              <li key={item.id}>{item.name}</li>
            ))}
          </ul>
        </section>
      ))}
    </div>
  );
}

Filtering Before Rendering

function ActiveUsers({ users }) {
  const activeUsers = users.filter(user => user.isActive);

  if (activeUsers.length === 0) {
    return <p>No active users.</p>;
  }

  return (
    <ul>
      {activeUsers.map(user => (
        <li key={user.id}>{user.name}{user.role}</li>
      ))}
    </ul>
  );
}

Event Handling

React wraps native DOM events in SyntheticEvents for cross-browser compatibility. Events use camelCase naming.

function EventExamples() {
  const handleClick = (e) => {
    console.log('Button clicked', e.target);
  };

  const handleChange = (e) => {
    console.log('Input value:', e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" onChange={handleChange} />
      <button type="button" onClick={handleClick}>Click me</button>
      <button type="submit">Submit</button>
    </form>
  );
}

Passing Arguments to Event Handlers

function ItemList({ items, onDelete }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          {/* Wrap in arrow function to pass arguments */}
          <button onClick={() => onDelete(item.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

Common Events

EventHTML EquivalentUse Case
onClickonclickButtons, links, clickable elements
onChangeonchangeInputs, selects, textareas
onSubmitonsubmitForm submission
onKeyDownonkeydownKeyboard shortcuts
onFocus / onBluronfocus / onblurFocus management
onMouseEnter / onMouseLeaveonmouseenter / onmouseleaveHover effects

Controlled vs Uncontrolled Components

Controlled Components

The component's value is controlled by React state. This is the recommended approach for forms.

function ControlledForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ name, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
      <input
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

Uncontrolled Components

The DOM itself manages the value. You use a ref to read the value when needed.

function UncontrolledForm() {
  const nameRef = useRef(null);
  const emailRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({
      name: nameRef.current.value,
      email: emailRef.current.value,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={nameRef} placeholder="Name" />
      <input ref={emailRef} placeholder="Email" />
      <button type="submit">Submit</button>
    </form>
  );
}

Controlled vs Uncontrolled

ControlledUncontrolled
Value sourceReact stateDOM
Update mechanismonChange + setStateDOM handles it
Read valueFrom state variableVia ref.current.value
ValidationOn every keystrokeOn submit
Best forDynamic forms, live validationSimple forms, file inputs

Thinking in React: Building a Complete Feature

Let's walk through how to build a feature the "React way".

Task: Build a searchable product list.

Step 1: Break the UI into a component hierarchy

FilterableProductTable
  |-- SearchBar
  |-- ProductTable
        |-- ProductCategoryRow
        |-- ProductRow

Step 2: Build a static version first (no state)

function FilterableProductTable({ products }) {
  return (
    <div>
      <SearchBar />
      <ProductTable products={products} />
    </div>
  );
}

function SearchBar() {
  return (
    <form>
      <input type="text" placeholder="Search..." />
      <label>
        <input type="checkbox" /> Only show in stock
      </label>
    </form>
  );
}

function ProductTable({ products }) {
  const categories = [...new Set(products.map(p => p.category))];

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>
        {categories.map(category => (
          <Fragment key={category}>
            <ProductCategoryRow category={category} />
            {products
              .filter(p => p.category === category)
              .map(product => (
                <ProductRow key={product.id} product={product} />
              ))}
          </Fragment>
        ))}
      </tbody>
    </table>
  );
}

Step 3: Identify the minimal state

  • Search text (changes based on user input)
  • "In stock only" checkbox (changes based on user input)
  • Product list — passed as props, NOT state

Step 4: Add state and wire it up

function FilterableProductTable({ products }) {
  const [searchText, setSearchText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);

  const filteredProducts = products.filter(product => {
    const matchesSearch = product.name
      .toLowerCase()
      .includes(searchText.toLowerCase());
    const matchesStock = !inStockOnly || product.inStock;
    return matchesSearch && matchesStock;
  });

  return (
    <div>
      <SearchBar
        searchText={searchText}
        inStockOnly={inStockOnly}
        onSearchChange={setSearchText}
        onStockChange={setInStockOnly}
      />
      <ProductTable products={filteredProducts} />
    </div>
  );
}

Step 5: State lives in the closest common ancestor of components that need it.

FilterableProductTable  <-- state lives here (both children need it)
  |-- SearchBar          <-- writes to state (via callbacks)
  |-- ProductTable       <-- reads from state (via filtered props)

Common Mistakes

1. Mutating State

// WRONG — mutating array
const [items, setItems] = useState(['a', 'b']);
items.push('c');      // Mutation! React won't know to re-render
setItems(items);      // Same reference — React skips update

// RIGHT — create new array
setItems([...items, 'c']);

2. Missing Key Prop

// WRONG — missing key
{items.map(item => <li>{item.name}</li>)}

// RIGHT — unique key
{items.map(item => <li key={item.id}>{item.name}</li>)}

3. Calling setState in Render

// WRONG — infinite loop
function Bad() {
  const [count, setCount] = useState(0);
  setCount(1);  // Called during render -> triggers re-render -> called again -> ...
  return <div>{count}</div>;
}

4. Using Stale State in Callbacks

// WRONG — stale closure
function Counter() {
  const [count, setCount] = useState(0);

  const incrementThree = () => {
    setCount(count + 1);  // count is 0
    setCount(count + 1);  // count is still 0
    setCount(count + 1);  // count is still 0 -> result: 1
  };

  // RIGHT — functional updates
  const incrementThree = () => {
    setCount(c => c + 1);  // 0 -> 1
    setCount(c => c + 1);  // 1 -> 2
    setCount(c => c + 1);  // 2 -> 3
  };
}

Interview Questions

Q1: What is the Virtual DOM and how does it work?

The Virtual DOM is a lightweight JavaScript representation of the actual DOM. When state changes, React creates a new Virtual DOM tree, compares it with the previous one (diffing), and applies only the minimal changes to the real DOM (reconciliation). This is faster than directly manipulating the DOM because batch updates reduce layout thrashing.

Q2: Why does React need keys in lists?

Keys give React a way to identify which items in a list have changed, been added, or removed. Without stable keys, React uses index by default, which can cause incorrect component reuse when items reorder. Keys should be stable, unique IDs from your data — not array indices.

Q3: What is the difference between props and state?

Props are external data passed from parent to child (read-only). State is internal data owned by the component (mutable via setter). Both trigger re-renders when they change. Props flow down the tree; state is local.

Q4: What happens when you call setState?

React schedules a re-render. The component function is called again, producing new JSX. React diffs the new virtual DOM against the old one and applies minimal DOM updates. Effects run after the DOM updates. If the new state is identical to the old state (by reference), React bails out and skips the re-render.

Q5: Why should components be pure?

Pure components produce the same output for the same input. This makes them predictable, testable, and allows React to optimize rendering (skip re-renders when props haven't changed). Side effects in render can cause infinite loops, stale data, and race conditions.

Q6: When should you use controlled vs uncontrolled components?

Use controlled components when you need real-time validation, conditional disabling, formatted input, or synchronized state across multiple fields. Use uncontrolled components for simple cases where you only need the value on submit, or for file inputs (which must be uncontrolled).

Q7: Explain React's reconciliation algorithm.

React uses a heuristic O(n) diffing algorithm:

  1. Elements of different types produce different trees (full rebuild)
  2. Elements of the same type are updated in place (only changed attributes)
  3. Keys identify child elements across renders (stable reordering)

This avoids the O(n^3) cost of generic tree diffing.

Q8: What are Fragments and why use them?

Fragments (<Fragment> or <>) let you return multiple elements without adding extra DOM nodes. This is important for valid HTML structure (e.g., table rows), CSS layouts (Flexbox/Grid direct children), and keeping the DOM clean. Use <Fragment> over <> when you need a key attribute.

Key Takeaways

  • Components are reusable UI building blocks — compose them into trees
  • JSX compiles to React.createElement() calls — it's JavaScript, not HTML
  • Props flow down, state is local — this is unidirectional data flow
  • Always use the setter function to update state — never mutate directly
  • Lists need unique, stable key props — not array indices
  • Understand the lifecycle: mount, update, unmount
  • Re-renders cascade from parent to children — use React.memo to optimize
  • Fragments avoid unnecessary DOM nodes
  • Prefer controlled components for forms
  • Think in React: break UI into components, find minimal state, lift state to common ancestor

Found this helpful?

Support devsofus — help us keep creating free dev guides.

Related Articles