Codepath

React useState Hook

Overview

The useState hook is one of React's core hooks that enables state management in functional components. State allows your components to "remember" data between renders and respond to user interactions.

This guide covers:

  • Understanding state in React
  • How useState works
  • When and how to use useState effectively
  • Common patterns, anti-patterns, and best practices

What is State in React?

State represents data that changes over time in your application. Unlike props (which are passed from parent components), state is managed within the component itself.

graph TD
    A[Component Renders] --> B[User Interacts]
    B --> C[State Changes]
    C --> D[Component Re-renders]
    D --> B

State is crucial for:

  • Tracking user input (form fields, toggles)
  • Controlling UI elements (modals, dropdowns)
  • Storing data from API requests
  • Managing application status (loading, error states)

📖 Read more: Thinking in React - Identify State

What is the useState Hook?

The useState hook is a function that:

  1. Creates a state variable with an initial value
  2. Returns an array containing the state variable and a function to update it
  3. Preserves this value between re-renders
  4. Triggers re-renders when the update function is called
graph LR
    A[useState] --> B[State Variable]
    A --> C[Update Function]
    C --> D[Update State]
    D --> E[Trigger Re-render]
    E --> B

useState Syntax

The useState hook is used to create a state variable with an initial value. It returns an array containing the state variable and a function to update it.

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

💡 Note: The state and setState are just values that you can name whatever you want. The convention is <variableName>, <"set" + VariableName>. See With Different Data Types for examples.

  • initialValue: The value when the component first renders
  • state: The current state value
  • setState: The function to update the state value

🎬 Watch: React useState Hook in 15 Minutes

When Should I Use useState?

Use useState when:

  1. Component data changes over time: Form inputs, toggles, counters
  2. Component needs to "remember" information: User selections, current tab
  3. UI changes based on user interactions: Show/hide elements, change styles
  4. Local component state doesn't need to be shared widely/globally: For shared state, consider Context API or state management libraries
function Counter() {
  // ✅ Good use of useState - local counter state
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

How Do I Declare a State Variable?

Basic Declaration

import React, { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
    </div>
  );
}

With Different Data Types

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

// String
const [name, setName] = useState('');

// Boolean
const [isActive, setIsActive] = useState(false);

// Array
const [items, setItems] = useState([]);

// Object
const [user, setUser] = useState({ name: '', age: 0 });

// null
const [selectedItem, setSelectedItem] = useState(null);

With Computed Initial State

If your initial state requires expensive computation, use a function inside useState to ensure it only runs once:

// ❌ Bad: This runs on every render
const [items, setItems] = useState(calculateExpensiveInitialState());

// ✅ Good: This runs only on first render
const [items, setItems] = useState(() => calculateExpensiveInitialState());

📖 Read more: Avoiding recreating the initial state

How Do I Update State?

Direct Updates

For simple values like numbers, strings, and booleans:

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

// Update to specific value
function resetCount() {
  setCount(0);
}

// Update based on current value
function increment() {
  setCount(count + 1);
}

Functional Updates

When the new state depends on the previous state, use the functional form:

// ❌ May lead to outdated state in some scenarios
function increment() {
  setCount(count + 1);
}

// ✅ Always uses latest state
function increment() {
  setCount(prevCount => prevCount + 1);
}

📖 Read more: Updating state based on the previous state

Updating Objects

React state updates are not merged automatically. For objects, you must preserve existing fields:

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

// ❌ Bad: Will remove the age property
function updateName(newName) {
  setUser({ name: newName });
}

// ✅ Good: Preserves other properties
function updateName(newName) {
  setUser(prevUser => ({ ...prevUser, name: newName }));
}

Updating Arrays

Use array methods that return new arrays rather than mutating existing ones:

const [items, setItems] = useState([1, 2, 3]);

// ✅ Add item
setItems([...items, 4]);

// ✅ Remove item
setItems(items.filter(item => item !== 2));

// ✅ Update item
setItems(items.map(item => item === 2 ? 20 : item));

📖 Read more: Updating Arrays in State

Common Mistakes with useState

1. Mutating State Directly

// ❌ Bad: Mutating state directly won't trigger re-renders
function addTodo(newTodo) {
  todos.push(newTodo); // This won't work!
}

// ✅ Good: Create new array
function addTodo(newTodo) {
  setTodos([...todos, newTodo]);
}

2. State Updates Are Not Immediate

State updates are batched and asynchronous. Don't rely on state being updated immediately after setting it.

// ❌ Won't work as expected
function increment() {
  setCount(count + 1);
  console.log(count); // Still has the old value!
}

// ✅ Use useEffect to respond to state changes
useEffect(() => {
  console.log("Count updated:", count);
}, [count]);

3. Multiple State Updates

React may batch multiple state updates for performance. Use functional updates to ensure correct sequencing.

// ❌ May only increment once if updates are batched
function incrementThrice() {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
}

// ✅ Will correctly increment three times
function incrementThrice() {
  setCount(c => c + 1);
  setCount(c => c + 1);
  setCount(c => c + 1);
}

4. Overusing useState

When you have multiple related state values, consider using a single object with useState or switching to useReducer.

// ❌ Too many useState calls for related data
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isValid, setIsValid] = useState(false);
const [errors, setErrors] = useState({});

// ✅ Better: Group related state
const [form, setForm] = useState({
  username: '',
  email: '',
  password: '',
  isValid: false,
  errors: {}
});

// Or even better for complex forms: useReducer.

📖 Read more: Choosing the State Structure

Resources

Fork me on GitHub