React Tutorial 0/120 lessons ~6 min read Lesson 32

    useState

    useState in React explains local component state, setters, functional updaters, snapshots, batching, immutability, arrays and objects, and when not to store derived values in state.

    Course progress0%
    Focus
    49 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    useState is the most commonly used React Hook for managing local component state.

    It allows a function component to remember information between renders and update the UI when that information changes.

    Examples of state include:

    • A counter value
    • Text entered into an input
    • Whether a menu is open
    • Whether a modal is visible
    • A selected tab
    • A checkbox value
    • Loading or error status
    • Data selected by the user
    text
    useState
    ↓
    Store component data
    ↓
    State changes
    ↓
    React schedules a render
    ↓
    UI reflects the new state

    What Is State?

    State is data that can change over time and whose changes may affect what a component renders.

    For example:

    javascript
    import { useState } from "react";
    function Counter() {
    const [count, setCount] = useState(0);
    return <p>Count: {count}</p>;
    }

    Here:

    • count is the current state value.
    • setCount is the function used to update the state.
    • 0 is the initial state value.

    When setCount updates the state, React renders the component again using the new value.

    A useful mental model is:

    text
    State
    ↓
    Render UI
    ↓
    User interaction
    ↓
    State update
    ↓
    React renders again
    ↓
    Updated UI

    Importing useState

    useState is imported from React:

    javascript
    import { useState } from "react";

    Then it can be used inside a function component:

    javascript
    function Counter() {
    const [count, setCount] = useState(0);
    return <p>{count}</p>;
    }

    Basic Syntax

    The basic syntax is:

    javascript
    const [state, setState] = useState(initialValue);

    For example:

    javascript
    const [count, setCount] = useState(0);

    The Hook returns two values:

    text
    [
    currentState,
    stateUpdater
    ]

    Array destructuring gives these values meaningful names.

    Conceptually:

    javascript
    const statePair = useState(0);
    const count = statePair[0];
    const setCount = statePair[1];

    The normal form is:

    javascript
    const [count, setCount] = useState(0);

    Understanding the Two Values

    1. Current State

    The first value represents the current state:

    javascript
    const [count, setCount] = useState(0);
    console.log(count);

    Initially:

    text
    count = 0

    After a state update, a subsequent render can see the new value.

    2. State Setter

    The second value is a function used to request a state update:

    javascript
    setCount(5);

    or:

    javascript
    setCount(count + 1);

    You should not directly modify state.

    Incorrect:

    javascript
    count = 5;

    Correct:

    javascript
    setCount(5);

    React uses the setter to schedule the update and render the component with the new state.

    A Complete Counter Example

    javascript
    import { useState } from "react";
    function Counter() {
    const [count, setCount] = useState(0);
    function increment() {
    setCount(count + 1);
    }
    function decrement() {
    setCount(count - 1);
    }
    return (
    <div>
    <h2>Counter</h2>
    <p>Count: {count}</p>
    <button onClick={increment}>+</button>
    <button onClick={decrement}>-</button>
    </div>
    );
    }
    export default Counter;

    The flow is:

    text
    Initial render
    ↓
    count = 0
    ↓
    User clicks +
    ↓
    setCount(count + 1)
    ↓
    React schedules update
    ↓
    Component renders again
    ↓
    count = 1
    ↓
    UI displays 1

    Why useState Is Needed

    Consider a normal JavaScript variable:

    javascript
    function Counter() {
    let count = 0;
    function increment() {
    count++;
    }
    return (
    <div>
    <p>{count}</p>
    <button onClick={increment}>+</button>
    </div>
    );
    }

    The JavaScript variable can change, but changing it does not tell React to render the component again.

    With useState:

    javascript
    const [count, setCount] = useState(0);

    React can associate the value with the component and schedule a new render when the setter is called.

    The important distinction is:

    text
    Normal variable
    ↓
    JavaScript value changes
    ↓
    No React state update

    versus:

    text
    State
    ↓
    Setter called
    ↓
    React schedules update
    ↓
    Component renders again

    State and Rendering

    React function components execute again when a state update causes a render.

    javascript
    function Counter() {
    const [count, setCount] = useState(0);
    console.log("Component rendered");
    return (
    <button onClick={() => setCount(count + 1)}>
    {count}
    </button>
    );
    }

    Initial render:

    text
    Component rendered
    count = 0

    After clicking:

    text
    setCount(...)
    ↓
    React schedules an update
    ↓
    Component function runs again
    ↓
    count = 1

    A re-render does not mean the entire browser page is rebuilt from scratch. React determines what needs to change in the rendered output and updates the necessary DOM.

    Initial State

    The value passed to useState is used as the initial state:

    javascript
    const [name, setName] = useState("John");
    javascript
    const [age, setAge] = useState(25);
    javascript
    const [isOpen, setIsOpen] = useState(false);
    javascript
    const [items, setItems] = useState([]);
    javascript
    const [user, setUser] = useState(null);

    Common initial values:

    DataExample
    NumberuseState(0)
    StringuseState("")
    BooleanuseState(false)
    ArrayuseState([])
    ObjectuseState({})
    Empty valueuseState(null)

    Choose an initial value that accurately represents the state before the component has received user input or other updates.

    Lazy Initial State

    If calculating the initial state is expensive, you can pass an initializer function:

    javascript
    const [items, setItems] = useState(() => createInitialItems());

    React uses the result of the initializer as the initial state.

    Compare:

    javascript
    const [items, setItems] = useState(createInitialItems());

    with:

    javascript
    const [items, setItems] = useState(() => createInitialItems());

    The second form tells React that createInitialItems is an initializer rather than a value that should be calculated on every render.

    For simple values, this is unnecessary:

    javascript
    const [count, setCount] = useState(0);

    Prefer the simple form unless initialization actually requires work.

    Updating State

    There are two common forms of state updates.

    Direct Value Update

    javascript
    setName("Alice");

    Use this when the next state does not need to be calculated from the previous state.

    Functional Updater

    javascript
    setCount(previousCount => previousCount + 1);

    Use this when the next state depends on the previous state.

    For example:

    javascript
    setCount(previousCount => previousCount + 1);

    The mental model is:

    text
    latest state
    ↓
    apply update function
    ↓
    new state

    Why Functional Updates Matter

    Consider:

    javascript
    function incrementThreeTimes() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    }

    All three expressions use the count value from the current render.

    If that render has:

    text
    count = 0

    each update can effectively request:

    text
    0 + 1

    A safer approach is:

    javascript
    function incrementThreeTimes() {
    setCount(previousCount => previousCount + 1);
    setCount(previousCount => previousCount + 1);
    setCount(previousCount => previousCount + 1);
    }

    Now the updates can be processed sequentially:

    text
    0 → 1 → 2 → 3

    Rule

    If the next state depends on the previous state, prefer:

    javascript
    setState(previousState => /* calculate next state */);

    This pattern is especially important for counters, toggles, queues, and other state transitions.

    State Is a Snapshot

    One of the most important React concepts is that state behaves like a snapshot for a particular render.

    Consider:

    javascript
    function Counter() {
    const [count, setCount] = useState(0);
    function handleClick() {
    setCount(count + 1);
    console.log(count);
    }
    return <button onClick={handleClick}>{count}</button>;
    }

    The console.log does not immediately show the new state value.

    If the current render has:

    text
    count = 0

    then the event handler sees:

    text
    count = 0

    Calling:

    javascript
    setCount(1);

    requests a future render.

    It does not change the count variable inside the already-running function.

    Mental model:

    text
    Current render
    count = 0
    ↓
    setCount(1)
    ↓
    request update
    ↓
    current function continues
    ↓
    next render
    count = 1

    This explains why state should not be treated like an ordinary mutable JavaScript variable.

    State Updates Are Requests for a New Render

    A useful way to think about:

    javascript
    setCount(10);

    is:

    React, please use 10 as the state value for a future render.

    It is not:

    text
    count = 10 immediately

    This distinction becomes especially important when several updates, event handlers, or asynchronous operations are involved.

    React Batches State Updates

    React can group multiple state updates together to process them efficiently.

    For example:

    javascript
    function handleClick() {
    setFirstName("John");
    setLastName("Doe");
    }

    React can process these updates together and render the resulting UI efficiently.

    Therefore, think of state setters as requests that participate in React's update process rather than synchronous assignments to local variables.

    State With Strings

    State can store text:

    javascript
    function Greeting() {
    const [name, setName] = useState("");
    return (
    <div>
    <input
    value={name}
    onChange={event => setName(event.target.value)}
    />
    <p>Hello, {name}</p>
    </div>
    );
    }

    The flow is:

    text
    User types
    ↓
    onChange event
    ↓
    setName(...)
    ↓
    state update
    ↓
    render
    ↓
    UI reflects new name

    This pattern is commonly used with controlled form components.

    State With Booleans

    Boolean state is useful for toggles and visibility.

    javascript
    function Menu() {
    const [isOpen, setIsOpen] = useState(false);
    function toggleMenu() {
    setIsOpen(previousOpen => !previousOpen);
    }
    return (
    <div>
    <button onClick={toggleMenu}>
    {isOpen ? "Close" : "Open"}
    </button>
    {isOpen && <nav>Menu content</nav>}
    </div>
    );
    }

    The state represents:

    text
    false → menu closed
    true → menu open

    Because the next value depends on the current value, the functional updater is a good choice:

    javascript
    setIsOpen(previousOpen => !previousOpen);

    State With Arrays

    State can store arrays:

    javascript
    const [items, setItems] = useState([]);

    To add an item:

    javascript
    setItems(previousItems => [
    ...previousItems,
    "React"
    ]);

    The spread operator creates a new array.

    Avoid mutating the existing array:

    javascript
    items.push("React");
    setItems(items);

    Prefer:

    javascript
    setItems(previousItems => [
    ...previousItems,
    "React"
    ]);

    React state should be updated using new values rather than directly mutating existing arrays.

    Removing an Item From an Array

    For example:

    javascript
    function TodoList() {
    const [items, setItems] = useState([
    { id: 1, text: "Learn React" },
    { id: 2, text: "Practice Hooks" }
    ]);
    function removeItem(id) {
    setItems(previousItems =>
    previousItems.filter(item => item.id !== id)
    );
    }
    return (
    <ul>
    {items.map(item => (
    <li key={item.id}>
    {item.text}
    <button onClick={() => removeItem(item.id)}>
    Remove
    </button>
    </li>
    ))}
    </ul>
    );
    }

    The filter method creates a new array without the removed item.

    State With Objects

    State can store objects:

    javascript
    const [user, setUser] = useState({
    name: "John",
    age: 25
    });

    To update only the name:

    javascript
    setUser(previousUser => ({
    ...previousUser,
    name: "Alice"
    }));

    The spread operator preserves the existing properties.

    Result:

    javascript
    {
    name: "Alice",
    age: 25
    }

    Avoid assuming that object state is automatically merged.

    For example:

    javascript
    setUser({
    name: "Alice"
    });

    replaces the state value with a new object containing only name.

    If you want to preserve existing properties, explicitly copy them:

    javascript
    setUser(previousUser => ({
    ...previousUser,
    name: "Alice"
    }));

    Nested Objects

    For nested state, copy each object level that needs to be changed.

    Example:

    javascript
    const [user, setUser] = useState({
    name: "John",
    address: {
    city: "Cuttack",
    country: "India"
    }
    });

    To update the city:

    javascript
    setUser(previousUser => ({
    ...previousUser,
    address: {
    ...previousUser.address,
    city: "Bhubaneswar"
    }
    }));

    The mental model is:

    text
    New user object
    ↓
    New address object
    ↓
    Updated city

    For deeply nested state, consider whether the state structure can be simplified.

    Multiple State Variables

    A component can have multiple pieces of state:

    javascript
    function Profile() {
    const [name, setName] = useState("");
    const [age, setAge] = useState(0);
    const [isEditing, setIsEditing] = useState(false);
    return (
    <div>
    <p>{name}</p>
    <p>{age}</p>
    <p>{isEditing ? "Editing" : "Viewing"}</p>
    </div>
    );
    }

    Each useState call represents an independent state value.

    Conceptually:

    text
    Profile component
    │
    ├── name
    ├── age
    └── isEditing

    This can make independent values easier to understand and update.

    Sometimes several values represent one logical object.

    For example:

    javascript
    const [form, setForm] = useState({
    name: "",
    email: "",
    city: ""
    });

    This can be appropriate when the values are closely related.

    Updating one property:

    javascript
    setForm(previousForm => ({
    ...previousForm,
    email: "user@example.com"
    }));

    However, grouping everything into one object is not automatically better.

    Prefer separate state when values have clearly independent behavior:

    javascript
    const [isOpen, setIsOpen] = useState(false);
    const [selectedTab, setSelectedTab] = useState("overview");

    Choose the structure that makes the state transitions easiest to understand.

    Avoid Unnecessary State

    Not every value needs to be stored in state.

    Suppose you have:

    javascript
    function UserProfile() {
    const [firstName, setFirstName] = useState("John");
    const [lastName, setLastName] = useState("Doe");
    const fullName = `${firstName} ${lastName}`;
    return <h2>{fullName}</h2>;
    }

    fullName does not need its own state because it can be calculated from existing state.

    Avoid:

    javascript
    const [fullName, setFullName] = useState("John Doe");

    when it can simply be derived:

    javascript
    const fullName = `${firstName} ${lastName}`;

    A useful rule is:

    If a value can be calculated reliably from existing props or state, it often does not need separate state.

    State Should Represent Information That Changes

    Good candidates for state:

    text
    User input
    Selection
    Visibility
    Toggle status
    Current step
    Loading status
    Fetched data
    Temporary UI state

    Poor candidates for state:

    text
    Values that never change
    Values directly derived from other state
    Values that can be calculated during rendering
    Constants
    Simple formatting results

    For example:

    javascript
    const price = 100;
    const taxRate = 0.18;
    const total = price + price * taxRate;

    There is no reason to make total state if it is always derived from price and taxRate.

    Do Not Store Props in State Without a Reason

    Consider:

    javascript
    function User({ name }) {
    const [userName, setUserName] = useState(name);
    return <h2>{userName}</h2>;
    }

    This creates two sources of information:

    text
    props.name
    state.userName

    If the prop changes later, the state does not automatically synchronize with it.

    Often the simpler solution is:

    javascript
    function User({ name }) {
    return <h2>{name}</h2>;
    }

    Use state when the component needs its own independently changing value, not simply because a prop exists.

    State and Props

    Props and state both influence rendering, but they have different purposes.

    PropsState
    Passed into a componentManaged by the component
    Read-only from the receiving componentUpdated using a setter
    Controlled by the parentControlled by the component
    Used to configure a componentUsed for changing component data

    Example:

    javascript
    function Counter({ initialCount }) {
    const [count, setCount] = useState(initialCount);
    return (
    <button onClick={() => setCount(previous => previous + 1)}>
    {count}
    </button>
    );
    }

    Here:

    text
    initialCount → prop
    count → state

    The parent supplies the starting value, while the component manages subsequent changes.

    State and Event Handling

    useState is commonly combined with event handlers.

    javascript
    function LikeButton() {
    const [liked, setLiked] = useState(false);
    function handleClick() {
    setLiked(previousLiked => !previousLiked);
    }
    return (
    <button onClick={handleClick}>
    {liked ? "Liked" : "Like"}
    </button>
    );
    }

    The relationship is:

    text
    User event
    ↓
    Event handler
    ↓
    State setter
    ↓
    New state
    ↓
    Render
    ↓
    Updated UI

    This pattern is fundamental to interactive React applications.

    State and Conditional Rendering

    State can determine which UI is rendered.

    javascript
    function LoginStatus() {
    const [isLoggedIn, setIsLoggedIn] = useState(false);
    return (
    <div>
    {isLoggedIn ? (
    <p>Welcome back!</p>
    ) : (
    <p>Please log in.</p>
    )}
    <button
    onClick={() => setIsLoggedIn(previous => !previous)}
    >
    Toggle Login
    </button>
    </div>
    );
    }

    The state controls the UI:

    text
    isLoggedIn = false
    ↓
    "Please log in."
    isLoggedIn = true
    ↓
    "Welcome back!"

    Resetting State

    Sometimes a component needs to reset state.

    javascript
    function Form() {
    const [name, setName] = useState("");
    function resetForm() {
    setName("");
    }
    return (
    <div>
    <input
    value={name}
    onChange={event => setName(event.target.value)}
    />
    <button onClick={resetForm}>
    Reset
    </button>
    </div>
    );
    }

    Resetting is simply another state update:

    javascript
    setName("");

    For multiple values:

    javascript
    setForm({
    name: "",
    email: "",
    city: ""
    });

    State Initializers Are Not Reset Values

    The initial value passed to useState is used to initialize the state.

    javascript
    const [count, setCount] = useState(10);

    Calling the component again as part of a normal re-render does not mean the state becomes 10 again.

    Once initialized, the state persists between renders.

    To reset it explicitly:

    javascript
    setCount(10);

    This distinction is important:

    text
    Initial value
    ↓
    Used to initialize state
    State setter
    ↓
    Used to change state later

    Preserving State Between Renders

    When a component re-renders, React preserves its state when the component remains in the same position in the UI tree.

    For example:

    javascript
    function Counter() {
    const [count, setCount] = useState(0);
    return (
    <button onClick={() => setCount(count + 1)}>
    {count}
    </button>
    );
    }

    A normal state update does not reset count to 0.

    Instead:

    text
    Initial render → 0
    Update → 1
    Update → 2
    Update → 3

    State is associated with the component's position in the rendered tree, not simply with the local variable declaration.

    State and Component Identity

    Consider:

    javascript
    function App() {
    return <Counter />;
    }

    As long as React considers the Counter component to be the same component in the same position, its state can be preserved between renders.

    Changing component identity or using different key values can cause state to be reset.

    This becomes especially important when working with lists and conditional component trees.

    State and Immutability

    React state should generally be treated as immutable.

    For arrays:

    javascript
    setItems(previousItems => [
    ...previousItems,
    newItem
    ]);

    For objects:

    javascript
    setUser(previousUser => ({
    ...previousUser,
    name: "Alice"
    }));

    The goal is to create a new state value instead of directly changing the existing one.

    Avoid:

    javascript
    user.name = "Alice";

    or:

    javascript
    items.push(newItem);

    Instead, create the next state:

    javascript
    setUser(previousUser => ({
    ...previousUser,
    name: "Alice"
    }));

    Comparing Old and New State

    React can use the identity of state values to determine whether an update results in a different value.

    For primitive values:

    javascript
    setCount(5);

    For objects and arrays, creating a new reference is generally important:

    javascript
    setUser(previousUser => ({
    ...previousUser,
    name: "Alice"
    }));

    rather than mutating the existing object.

    This is one reason immutable update patterns are so common in React.

    Functional Updaters for Reliable Transitions

    Functional updates are useful whenever state changes are expressed as transitions.

    Counter:

    javascript
    setCount(previous => previous + 1);

    Toggle:

    javascript
    setOpen(previous => !previous);

    Array addition:

    javascript
    setItems(previous => [...previous, item]);

    Array removal:

    javascript
    setItems(previous =>
    previous.filter(item => item.id !== id)
    );

    Object update:

    javascript
    setUser(previous => ({
    ...previous,
    active: true
    }));

    The common pattern is:

    text
    previous state
    ↓
    calculate next state
    ↓
    return next state

    Common useState Mistakes

    Mistake 1: Mutating State Directly

    Incorrect:

    javascript
    count++;

    Correct:

    javascript
    setCount(previous => previous + 1);

    Mistake 2: Mutating Arrays

    Incorrect:

    javascript
    items.push("React");
    setItems(items);

    Correct:

    javascript
    setItems(previous => [
    ...previous,
    "React"
    ]);

    Mistake 3: Mutating Objects

    Incorrect:

    javascript
    user.name = "Alice";
    setUser(user);

    Correct:

    javascript
    setUser(previous => ({
    ...previous,
    name: "Alice"
    }));

    Mistake 4: Expecting Immediate State Changes

    Incorrect mental model:

    javascript
    setCount(10);
    console.log(count); // expecting 10

    The current render's count value does not change in place.

    Think:

    text
    setCount(10)
    ↓
    request next state
    ↓
    future render

    Mistake 5: Using Current State for Repeated Updates

    Instead of:

    javascript
    setCount(count + 1);
    setCount(count + 1);

    prefer:

    javascript
    setCount(previous => previous + 1);
    setCount(previous => previous + 1);

    when each update depends on the previous result.

    Mistake 6: Storing Derived Data

    Avoid:

    javascript
    const [firstName, setFirstName] = useState("John");
    const [lastName, setLastName] = useState("Doe");
    const [fullName, setFullName] = useState("John Doe");

    Prefer:

    javascript
    const [firstName, setFirstName] = useState("John");
    const [lastName, setLastName] = useState("Doe");
    const fullName = `${firstName} ${lastName}`;

    This avoids duplicated sources of truth.

    Mistake 7: Putting Too Much Into One State Object

    This:

    javascript
    const [state, setState] = useState({
    name: "",
    email: "",
    isOpen: false,
    selectedTab: "home",
    loading: false
    });

    may become difficult to manage if these values have unrelated behavior.

    Sometimes separate state is clearer:

    javascript
    const [name, setName] = useState("");
    const [email, setEmail] = useState("");
    const [isOpen, setIsOpen] = useState(false);
    const [selectedTab, setSelectedTab] = useState("home");
    const [loading, setLoading] = useState(false);

    The right choice depends on how the state changes together.

    Practical Example: Character Counter

    javascript
    import { useState } from "react";
    function CharacterCounter() {
    const [text, setText] = useState("");
    return (
    <div>
    <textarea
    value={text}
    onChange={event => setText(event.target.value)}
    />
    <p>Characters: {text.length}</p>
    </div>
    );
    }
    export default CharacterCounter;

    Notice that the character count does not need its own state.

    javascript
    const characterCount = text.length;

    This is derived data.

    Practical Example: Todo List

    javascript
    import { useState } from "react";
    function TodoList() {
    const [text, setText] = useState("");
    const [todos, setTodos] = useState([]);
    function addTodo() {
    if (!text.trim()) {
    return;
    }
    const todo = {
    id: Date.now(),
    text: text.trim()
    };
    setTodos(previousTodos => [
    ...previousTodos,
    todo
    ]);
    setText("");
    }
    function removeTodo(id) {
    setTodos(previousTodos =>
    previousTodos.filter(todo => todo.id !== id)
    );
    }
    return (
    <div>
    <input
    value={text}
    onChange={event => setText(event.target.value)}
    placeholder="Enter a task"
    />
    <button onClick={addTodo}>
    Add
    </button>
    <ul>
    {todos.map(todo => (
    <li key={todo.id}>
    {todo.text}
    <button onClick={() => removeTodo(todo.id)}>
    Remove
    </button>
    </li>
    ))}
    </ul>
    </div>
    );
    }
    export default TodoList;

    This example demonstrates:

    • String state
    • Array state
    • Functional state updates
    • Immutable array updates
    • Derived UI
    • Event handling
    • Controlled input
    • State reset

    Practical Example: Form State

    A small form can use an object as state:

    javascript
    import { useState } from "react";
    function UserForm() {
    const [form, setForm] = useState({
    name: "",
    email: ""
    });
    function handleChange(event) {
    const { name, value } = event.target;
    setForm(previousForm => ({
    ...previousForm,
    [name]: value
    }));
    }
    function handleSubmit(event) {
    event.preventDefault();
    console.log(form);
    }
    return (
    <form onSubmit={handleSubmit}>
    <input
    name="name"
    value={form.name}
    onChange={handleChange}
    placeholder="Name"
    />
    <input
    name="email"
    value={form.email}
    onChange={handleChange}
    placeholder="Email"
    />
    <button type="submit">
    Submit
    </button>
    </form>
    );
    }
    export default UserForm;

    The computed property:

    javascript
    [name]: value

    allows one handler to update different fields.

    Practical Example: Shopping Cart Quantity

    javascript
    import { useState } from "react";
    function CartItem() {
    const [quantity, setQuantity] = useState(1);
    function increase() {
    setQuantity(previous => previous + 1);
    }
    function decrease() {
    setQuantity(previous => Math.max(1, previous - 1));
    }
    return (
    <div>
    <button onClick={decrease}>-</button>
    <span>{quantity}</span>
    <button onClick={increase}>+</button>
    </div>
    );
    }
    export default CartItem;

    The updater form makes the state transition explicit:

    text
    quantity
    ↓
    previous quantity
    ↓
    +1 or -1
    ↓
    next quantity

    When Should You Use useState?

    Use useState when:

    • A value changes over time.
    • The value affects rendered UI.
    • The state belongs to a particular component or small component subtree.
    • User interactions should update the value.
    • The component needs to remember information between renders.

    Examples:

    text
    ✓ Input value
    ✓ Open/closed state
    ✓ Selected option
    ✓ Counter
    ✓ Local form data
    ✓ Current tab
    ✓ Temporary UI state

    When Should You Not Use useState?

    You may not need useState when:

    The value is constant

    javascript
    const appName = "React App";

    The value can be derived

    javascript
    const fullName = `${firstName} ${lastName}`;

    The value does not affect rendering

    Some values are better suited to useRef, which will be covered in a later lesson.

    The state belongs to another component

    Sometimes state should be lifted to a parent component and passed down as props.

    These decisions become increasingly important as React applications grow.

    useState and State Ownership

    A key architectural question is:

    Which component should own this state?

    Suppose two components need access to the same selected product:

    text
    App
    ├── ProductList
    └── ProductDetails

    If both need the selected product, the state may belong in App:

    javascript
    function App() {
    const [selectedProduct, setSelectedProduct] = useState(null);
    return (
    <>
    <ProductList
    onSelect={setSelectedProduct}
    />
    <ProductDetails
    product={selectedProduct}
    />
    </>
    );
    }

    This is an introduction to lifting state up, an important React design pattern.

    State and Reusability

    Good component design usually keeps state close to where it is needed.

    For example, a dropdown's open/closed state may belong inside the dropdown:

    javascript
    function Dropdown() {
    const [isOpen, setIsOpen] = useState(false);
    // ...
    }

    If unrelated components need the same state, it may need to move higher in the component tree.

    The goal is not to put all state at the top.

    Instead:

    text
    Keep state as local as possible
    ↓
    Lift it when multiple components need it

    Best Practices for useState

    1. Give State Meaningful Names

    Prefer:

    javascript
    const [isOpen, setIsOpen] = useState(false);

    over:

    javascript
    const [x, setX] = useState(false);

    2. Match the Initial Value to the Data

    javascript
    const [items, setItems] = useState([]);
    javascript
    const [isLoading, setIsLoading] = useState(false);
    javascript
    const [user, setUser] = useState(null);

    3. Use Functional Updates When Based on Previous State

    javascript
    setCount(previous => previous + 1);

    4. Avoid Direct Mutation

    Create new arrays and objects when updating them.

    5. Avoid Duplicate State

    If data can be derived, calculate it rather than storing a second copy.

    6. Keep State Local When Possible

    Do not move state to a global solution unless the application's requirements actually call for it.

    7. Keep State Structure Simple

    State should make the component easier to reason about, not harder.

    useState Mental Model

    A useful mental model is:

    text
    ┌───────────────┐
    │ State │
    └───────┬───────┘
    │
    ▼
    ┌───────────────┐
    │ Render │
    └───────┬───────┘
    │
    ▼
    ┌───────────────┐
    │ UI │
    └───────┬───────┘
    │
    User interaction
    │
    ▼
    ┌───────────────┐
    │ setState(...) │
    └───────┬───────┘
    │
    ▼
    ┌───────────────┐
    │ Next Render │
    └───────────────┘

    The most important concept is:

    State drives rendering, and state setters request the next state.

    Quick Comparison

    ApproachPurpose
    let valueTemporary JavaScript variable
    const valueConstant reference
    useStateState that participates in rendering
    useRefPersistent mutable value that does not itself trigger a render
    PropsData passed into a component

    A later lesson will explore useRef in detail.

    Interview questions & answers

    Interview preparation

    10 questions grouped by difficulty — expand any card for a model answer and follow-up probe.

    Beginner

    4
    1. 1What is useState?
      Beginner

      Model answer

      useState is a React Hook that allows function components to manage state.
    2. 2What is the difference between state and a normal variable?
      Beginner

      Model answer

      A normal variable does not persist as component state between renders and changing it does not tell React to render updated UI.

      State is preserved by React and updating it can trigger a render.

    3. 3Does useState immediately change the current state variable?
      Beginner

      Model answer

      No.

      Calling the setter requests an update for a future render.

      The current render continues with its existing state snapshot.

    4. 4What is lazy initialization?
      Beginner

      Model answer

      Instead of passing an already calculated value, you can provide an initializer function: const [value, setValue] = useState(() => calculateInitialValue()); This is useful when initial-state calculation is expensive.

    Intermediate

    3
    1. 5What does useState return?
      Intermediate

      Model answer

      It returns an array containing: [currentState, stateSetter]
    2. 6When should you use the functional updater form?
      Intermediate

      Model answer

      When the next state depends on the previous state: setCount(previous => previous + 1);
    3. 7Can a component have multiple useState calls?
      Intermediate

      Model answer

      Yes. const [name, setName] = useState(""); → const [age, setAge] = useState(0); → const [isOpen, setIsOpen] = useState(false);

    Advanced

    3
    1. 8Why should you use the setter instead of modifying state directly?
      Advanced

      Model answer

      The setter tells React that the state should change and allows React to schedule a new render.

    2. 9Why shouldn't you mutate arrays or objects in state?
      Advanced

      Model answer

      Direct mutation can produce incorrect update patterns and makes state changes harder for React and developers to reason about.

      Prefer creating new arrays or objects.

    3. 10Should derived values always be stored in state?
      Advanced

      Model answer

      No.

      If a value can be calculated from existing props or state, it often should be derived during rendering instead.

    Practice Exercises

    Exercise 1: Counter

    Create a counter with:

    • Increment button
    • Decrement button
    • Reset button
    • Current count display

    Use functional updates where appropriate.

    Exercise 2: Toggle

    Create a component that:

    • Displays "ON" or "OFF"
    • Has a toggle button
    • Uses boolean state

    Exercise 3: Character Counter

    Create a textarea that:

    • Stores its value in state
    • Displays the current character count
    • Updates as the user types

    Do not create separate state for the character count.

    Exercise 4: Todo List

    Create a todo list that supports:

    • Adding todos
    • Removing todos
    • Displaying all todos
    • Clearing the input after adding

    Use immutable array updates.

    Exercise 5: User Form

    Create a form with:

    • Name
    • Email
    • City

    Store the form values in an object state and update fields using a reusable change handler.

    Exercise 6: Shopping Cart Quantity

    Create a quantity selector with:

    • Increment
    • Decrement
    • Minimum quantity of 1

    Use functional state updates.

    Mini Project: Like Button

    Build a reusable Like Button component.

    Requirements:

    text
    Initial state:
    liked = false
    Click:
    false → true
    true → false

    Display:

    text
    ♡ Like

    when not liked, and:

    text
    ♥ Liked

    when liked.

    A possible implementation:

    javascript
    import { useState } from "react";
    function LikeButton() {
    const [liked, setLiked] = useState(false);
    function handleClick() {
    setLiked(previousLiked => !previousLiked);
    }
    return (
    <button onClick={handleClick}>
    {liked ? "♥ Liked" : "♡ Like"}
    </button>
    );
    }
    export default LikeButton;

    This small example demonstrates the complete state cycle:

    text
    Initial state
    ↓
    false
    ↓
    User clicks
    ↓
    setLiked(...)
    ↓
    true
    ↓
    React renders
    ↓
    UI changes

    Quick Cheat Sheet

    Import

    javascript
    import { useState } from "react";

    Basic state

    javascript
    const [count, setCount] = useState(0);

    Set a value

    javascript
    setCount(10);

    Update from previous state

    javascript
    setCount(previous => previous + 1);

    Boolean toggle

    javascript
    setOpen(previous => !previous);

    Array update

    javascript
    setItems(previous => [
    ...previous,
    item
    ]);

    Array removal

    javascript
    setItems(previous =>
    previous.filter(item => item.id !== id)
    );

    Object update

    javascript
    setUser(previous => ({
    ...previous,
    name: "Alice"
    }));

    Avoid mutation

    javascript
    // Avoid
    items.push(item);
    // Prefer
    setItems(previous => [...previous, item]);

    Derived value

    javascript
    const fullName = `${firstName} ${lastName}`;

    Key Takeaways

    Ready to mark this lesson complete?Track your journey across the entire course.