[React Basics] useState Hook: How to Manage State Effectively in React

If you know React, you’ve probably heard of "state." State is the "brain" of a component, storing data that can change and determining what the component displays. In the past, you had to use Class Components to work with state. But since React 16.8, a revolution began with the introduction of Hooks, and useState is the most basic and important Hook, ushering in the era of Functional Components.

useState Hook: How to Manage State Effectively in React

This article will help you master useState, from the basics to advanced techniques. Let’s dive in!

What is the useState Hook? 💡

Imagine a Functional Component as someone with short-term memory. Every time it re-renders, it forgets everything. useState is a Hook (a special function) that lets you "plug in" memory to that Functional Component. Now, the component can remember, read, and update its state across renders—without writing a class.

Simply put, useState brings state management to function components, making your code cleaner, more readable, and easier to maintain.

Syntax and How It Works ✨

To use useState, first import it from React:

import React, { useState } from 'react'

The basic syntax of useState looks like this:

const [state, setState] = useState(initialState)

Let’s break it down:

  • initialState: The initial value for your state. It can be anything: a number, string, boolean, object, or array. React only uses this value on the first render.
  • state: 📦 The variable holding the current state value. You can name it anything (e.g., count, userName, isActive).
  • setState: ⚙️ A special function React provides to update the state. When you call it with a new value, React schedules a re-render with the updated state. By convention, this is named set + StateVariable (e.g., setCount, setUserName).
  • [...] (Array Destructuring): This JavaScript syntax "unpacks" the two elements returned by useState (the state value and the updater function) into separate variables.

Classic Example: Counter

This is the "Hello, World!" of useState and will help you understand it instantly.

import React, { useState } from 'react'

function Counter() {
  // 1. Declare a new state variable called "count"
  //    Initial value is 0
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>You clicked {count} times</p>

      {/* 2. On click, call setCount to update state */}
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  )
}

export default Counter

How it works:

  1. First render: useState(0) is called. count is 0. The component displays "You clicked 0 times."
  2. When you click the button: The onClick handler runs, calling setCount(count + 1) (i.e., setCount(1)).
  3. React schedules a re-render: React sees the state has changed and re-renders the Counter component.
  4. Second render: useState is called again, but React knows this component already has state, so it returns the current value, which is 1. Now count is 1. The component displays "You clicked 1 time."

This process repeats every time you click.

Advanced Concepts and Golden Rules 🏆

Using useState isn’t just about counting. To become a "pro," you need to master these rules and techniques.

1. Updating State That’s an Object or Array

This is a common beginner mistake. Golden rule: Never mutate state directly! Always create a new object or array.

Why? React compares the old and new state to decide whether to re-render. If you only change a property inside the old object, React sees the same reference and may skip the re-render.

For Objects:

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

// ❌ WRONG: Mutating directly
// const handleAgeIncrease = () => {
//   user.age = user.age + 1;
//   setUser(user); // React may not re-render
// };

// ✅ RIGHT: Use spread syntax (...) to create a new object
const handleAgeIncrease = () => {
  setUser({ ...user, age: user.age + 1 })
}

For Arrays:

const [todos, setTodos] = useState(['Learn React', 'Go to sleep'])

// ❌ WRONG: Mutating directly
// const addTodo = () => {
//   todos.push('Do homework');
//   setTodos(todos); // React may not re-render
// };

// ✅ RIGHT: Use spread syntax (...) to create a new array
const addTodo = (newTodo) => {
  setTodos([...todos, newTodo])
}

2. Updating State Based on Previous State

Imagine you have a button that increases a value twice in a row:

const [count, setCount] = useState(0)

const handleIncreaseByTwo = () => {
  setCount(count + 1) // count is still 0 here
  setCount(count + 1) // count is still 0 here
}
// Result: count only increases by 1, not 2!

This happens because state updates in React are asynchronous. React may batch multiple setCount calls for performance. Both calls read count as 0.

Solution: Pass a function to setState. This function receives the previous state (prevState) and returns the new state. This is the safe, recommended way when the new state depends on the old state.

const handleIncreaseByTwoCorrectly = () => {
  setCount((prevCount) => prevCount + 1)
  setCount((prevCount) => prevCount + 1)
}
// Result: count increases by 2!

3. The Golden Rules of Hooks ⚠️

React enforces 2 rules when using Hooks:

  1. Only call Hooks at the top level: Don’t call Hooks inside loops, conditions, or nested functions.
    • Why: React relies on the order of Hook calls to match state to each Hook.
  2. Only call Hooks from React Functional Components: Don’t call Hooks from regular JavaScript functions.

Practical Example: Simple Login Form

Let’s apply what you’ve learned to build a simple login form, managing state for both username and password.

import React, { useState } from 'react'

function LoginForm() {
  const [formData, setFormData] = useState({
    username: '',
    password: '',
  })

  const handleChange = (event) => {
    const { name, value } = event.target

    // Use functional update and spread syntax for safe updates
    setFormData((prevData) => ({
      ...prevData,
      [name]: value, // Use computed property name
    }))
  }

  const handleSubmit = (event) => {
    event.preventDefault() // Prevent page reload
    alert(
      `Logging in with Username: ${formData.username} and Password: ${formData.password}`,
    )
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input
          type="text"
          name="username"
          value={formData.username}
          onChange={handleChange}
        />
      </label>
      <br />
      <label>
        Password:
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
        />
      </label>
      <br />
      <button type="submit">Login</button>
    </form>
  )
}

In this example, we use a single state object to manage all form data—a very common and effective technique.

Conclusion: useState is the Foundation of React Apps

useState is a simple Hook in terms of syntax but incredibly powerful. It’s the building block for modern React apps. By understanding how it works, the rules for using it, and common patterns, you have a great tool for creating flexible, manageable components.

From here, you’re ready to explore other Hooks like useEffect for side effects, useContext for global state, and more.

Congratulations on taking your first step with React Hooks!

Related Posts

[React Basics] React Context: Concept & Most Effective Usage

What is React Context? Learn how to use the React Context API to manage global state easily and efficiently, eliminate Prop Drilling, and optimize your React apps.

[React Basics] The Most Effective Ways to Use Conditional Rendering in React

Level up your React skills with Conditional Rendering. This guide explains how to display different elements based on conditions and optimize your app’s performance.

[React Basics] Stateful vs. Stateless Components: Differences and Usage in React

What are the core differences between Stateful and Stateless Components in React? Learn when and why to use each type to optimize your app’s performance.

[React Basics] Managing Complex State with the useReducer Hook in React

Discover the useReducer hook—a powerful solution for managing complex state in React. This article will help you understand its principles and how to apply it effectively.