Tell us about the useState, how does it work, where is it applied and what is it for?

👨‍💻 Frontend Developer 🟠 May come up 🎚️ Easy
#React #Hooks

Brief Answer

useState is a React hook that adds local state to functional components. It returns the current state value and a function to update it.

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

Key features:

  • Allows using state without class components
  • The state update function replaces the old value with a new one
  • Calling the state update function triggers a new component render
  • Multiple independent useState hooks can be used in one component

Usage example:

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Increase counter
      </button>
    </div>
  );
}

Full Answer

The useState hook is one of React’s core tools, introduced in version 16.8. It allows adding and managing state in functional components, which was previously only possible in class components using this.state. 🚀

How useState works?

useState returns an array of two elements:

  1. Current state value
  2. Function to update this state
import React, { useState } from 'react';
 
function Example() {
  // Destructuring the array returned by useState
  const [count, setCount] = useState(0);
  
  console.log('Current state:', count);
  
  return (
    <div>
      <p>Value: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
}

When the state update function is called (setCount in the example above), React:

  1. Updates the state value
  2. Triggers a component re-render
  3. Returns the updated value on the next render

useState Features

1. Functional Updates

If the new state depends on the previous one, it’s better to use the functional form:

function Counter() {
  const [count, setCount] = useState(0);
  
  const handleIncrement = () => {
    // ❌ Can be problematic with rapid clicks
    // setCount(count + 1);
    
    // ✅ Guarantees using the current state
    setCount(prevCount => prevCount + 1);
  };
  
  return (
    <button onClick={handleIncrement}>
      Increase: {count}
    </button>
  );
}

2. Lazy Initialization

If the initial state requires calculations, you can pass a function:

function ExpensiveInitialState() {
  // Function is called only on the first render
  const [state, setState] = useState(() => {
    console.log('Calculating initial state...');
    return calculateExpensiveValue();
  });
  
  return (
    <div>
      <p>Value: {state}</p>
      <button onClick={() => setState(state + 1)}>
        Update
      </button>
    </div>
  );
}

3. Comparison with Previous Values

React uses Object.is to compare values:

function ObjectStateExample() {
  const [person, setPerson] = useState({ name: 'John', age: 30 });
  
  const updateAge = () => {
    // ❌ Won't trigger re-render if age is the same
    // person.age = 31;
    // setPerson(person);
    
    // ✅ Creates a new object, triggers re-render
    setPerson({ ...person, age: 31 });
  };
  
  return (
    <div>
      <p>Name: {person.name}, Age: {person.age}</p>
      <button onClick={updateAge}>
        Update age
      </button>
    </div>
  );
}

Using useState with Different Data Types

1. Primitive Types (strings, numbers, booleans)

function FormExample() {
  const [name, setName] = useState('');
  const [age, setAge] = useState(0);
  const [isActive, setIsActive] = useState(false);
  
  return (
    <form>
      <input 
        value={name}
        onChange={e => setName(e.target.value)}
        placeholder="Enter name"
      />
      <input 
        type="number"
        value={age}
        onChange={e => setAge(Number(e.target.value))}
        placeholder="Enter age"
      />
      <label>
        <input
          type="checkbox"
          checked={isActive}
          onChange={e => setIsActive(e.target.checked)}
        />
        Active
      </label>
    </form>
  );
}

2. Arrays

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');
  
  const addTodo = () => {
    if (input.trim()) {
      // Create a new array with the new item
      setTodos([...todos, input]);
      setInput('');
    }
  };
  
  const removeTodo = (index) => {
    // Filter the array, excluding the item by index
    setTodos(todos.filter((_, i) => i !== index));
  };
  
  return (
    <div>
      <input
        value={input}
        onChange={e => setInput(e.target.value)}
      />
      <button onClick={addTodo}>Add</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            {todo}
            <button onClick={() => removeTodo(index)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

3. Objects

function UserProfile() {
  const [profile, setProfile] = useState({
    firstName: '',
    lastName: '',
    email: ''
  });
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    
    // Update only the changed field, preserving the rest
    setProfile(prevProfile => ({
      ...prevProfile,
      [name]: value
    }));
  };
  
  return (
    <form>
      <input
        name="firstName"
        value={profile.firstName}
        onChange={handleChange}
        placeholder="First Name"
      />
      <input
        name="lastName"
        value={profile.lastName}
        onChange={handleChange}
        placeholder="Last Name"
      />
      <input
        name="email"
        value={profile.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <div>
        <strong>Profile:</strong> 
        {JSON.stringify(profile, null, 2)}
      </div>
    </form>
  );
}

Comparison with Class Components

// Class component
class ClassCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  render() {
    return (
      <div>
        <p>Counter: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increase
        </button>
      </div>
    );
  }
}
 
// Functional component with useState
function HookCounter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Counter: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
}

Optimization and Best Practices

// ❌ Many separate states
function UserFormBad() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');
  
  // Many handlers
}
 
// ✅ Grouping related states
function UserFormGood() {
  const [user, setUser] = useState({
    firstName: '',
    lastName: '',
    email: '',
    phone: ''
  });
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setUser(prevUser => ({
      ...prevUser,
      [name]: value
    }));
  };
  
  // One handler for all fields
}

2. Avoiding Unnecessary Re-renders

function ExpensiveCalculation({ data }) {
  const [count, setCount] = useState(0);
  
  // Heavy calculation is called on every render
  const expensiveResult = calculateExpensive(data);
  
  return (
    <div>
      <p>Result: {expensiveResult}</p>
      <button onClick={() => setCount(count + 1)}>
        Counter: {count}
      </button>
    </div>
  );
}
 
// Solution: use useMemo with useState
function OptimizedCalculation({ data }) {
  const [count, setCount] = useState(0);
  
  // Calculation is performed only when data changes
  const expensiveResult = useMemo(() => {
    return calculateExpensive(data);
  }, [data]);
  
  return (
    <div>
      <p>Result: {expensiveResult}</p>
      <button onClick={() => setCount(count + 1)}>
        Counter: {count}
      </button>
    </div>
  );
}

Common Mistakes

1. Direct Mutation of State Objects

// ❌ Incorrect: direct mutation
function WrongMutation() {
  const [user, setUser] = useState({ name: 'John', age: 30 });
  
  const handleClick = () => {
    // This will NOT trigger a re-render!
    user.age = 31;
    setUser(user);
  };
  
  return (
    <div>
      <p>Name: {user.name}, Age: {user.age}</p>
      <button onClick={handleClick}>Update age</button>
    </div>
  );
}
 
// ✅ Correct: creating a new object
function CorrectUpdate() {
  const [user, setUser] = useState({ name: 'John', age: 30 });
  
  const handleClick = () => {
    // Create a new object
    setUser({ ...user, age: 31 });
  };
  
  return (
    <div>
      <p>Name: {user.name}, Age: {user.age}</p>
      <button onClick={handleClick}>Update age</button>
    </div>
  );
}

2. Asynchronous Nature of setState

// ❌ Incorrect: relying on immediate update
function AsyncProblem() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // Still the old value here!
  };
  
  return (
    <button onClick={handleClick}>
      Increase: {count}
    </button>
  );
}
 
// ✅ Correct: use useEffect to react to changes
function CorrectAsync() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log("Counter updated:", count);
  }, [count]);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Increase: {count}
    </button>
  );
}

3. Errors with State Updates Dependent on Previous Values

// ❌ Incorrect: may lead to skipped updates
function MultipleUpdatesWrong() {
  const [count, setCount] = useState(0);
  
  const handleMultipleClicks = () => {
    // All these calls use the same count value
    setCount(count + 1); // count = 0 -> 1
    setCount(count + 1); // count = 0 -> 1
    setCount(count + 1); // count = 0 -> 1
    // Result: count = 1, not 3!
  };
  
  return (
    <button onClick={handleMultipleClicks}>
      Multiple updates: {count}
    </button>
  );
}
 
// ✅ Correct: use functional form
function MultipleUpdatesCorrect() {
  const [count, setCount] = useState(0);
  
  const handleMultipleClicks = () => {
    // Each call gets the current previous value
    setCount(prev => prev + 1); // 0 -> 1
    setCount(prev => prev + 1); // 1 -> 2
    setCount(prev => prev + 1); // 2 -> 3
    // Result: count = 3
  };
  
  return (
    <button onClick={handleMultipleClicks}>
      Multiple updates: {count}
    </button>
  );
}

Summary

useState is React’s main hook for:

  • Adding local state to functional components
  • Managing simple and complex data (primitives, objects, arrays)
  • Tracking user input and interaction
  • Replacing this.state and this.setState from class components

Key features:

  • Easy to use: const [state, setState] = useState(initialValue)
  • Supports functional updates: setState(prev => newValue)
  • Allows lazy initialization: useState(() => computeValue())
  • Each useState call creates independent state
  • State changes trigger component re-renders

Best practices:

  • Use functional updates for values that depend on previous ones
  • Group related states into objects
  • Never mutate state objects directly
  • Combine with other hooks for more complex logic (useMemo, useCallback)

useState is a fundamental React hook that makes functional components more powerful and flexible, allowing them to manage internal state as effectively as class components. 🚀


Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪