React

React useState — Managing Component State

Thirdy Gayares
20 min read

🎓 What You Will Learn

  • useState Basics: Add state to functional components
  • State Updates: How to change state safely
  • Multiple States: Manage different pieces of data
  • Form Handling: Handle input and form state
  • Array State: Add, remove, and update array items
  • Object State: Update nested object properties
  • Conditional Rendering: Show/hide based on state
  • Best Practices: How to use useState correctly
1

What is useState?

useState is a React Hook that lets you add state to functional components. Before hooks, you had to use class components to have state. Now, any functional component can have state using useState.

State is data that can change over time. When state changes, React automatically re-renders the component to show the new data.

Simple formula: const [value, setValue] = useState(initialValue)
Returns: An array with the current value and a function to update it
2

Basic Syntax

useState-syntax.jsx
import { useState } from 'react';

export function Counter() {
  // Declare state variable
  const [count, setCount] = useState(0);
  //    ^       ^           ^
  //    |       |           initial value
  //    |       function to update
  //    current value

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}
▶ Try it YourselfCounter

Current Count:

0

Counter.jsx
import { useState } from 'react';

export 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>
  );
}
3

How State Updates Work

When you call the setter function (setCount), React:

  1. 1. Updates the state value
  2. 2. Re-renders the component with the new value
  3. 3. Browser updates the display
✅ Always use the setter function! Never mutate state directly. count = count + 1 won't work. Use setCount(count + 1) instead.
▶ Try it YourselfForm Input

Start typing...

FormInput.jsx
import { useState } from 'react';

export function NameInput() {
  const [name, setName] = useState("");

  return (
    <div>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter name"
      />
      {name && <p>Hello, {name}!</p>}
    </div>
  );
}
4

Form Inputs & State

To capture form input, store it in state and update it with onChange event.

form-state.jsx
export function LoginForm() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("Username:", username);
    console.log("Password:", password);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
        placeholder="Username"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button type="submit">Login</button>
    </form>
  );
}
▶ Try it YourselfToggle Visibility
Toggle.jsx
import { useState } from 'react';

export function ToggleMessage() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <>
      <button onClick={() => setIsVisible(!isVisible)}>
        {isVisible ? "Hide" : "Show"}
      </button>

      {isVisible && <p>Message is visible!</p>}
    </>
  );
}
5

Multiple State Variables

You can use useState multiple times in the same component. Each state variable is independent.

multiple-usestate.jsx
export function PersonForm() {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [age, setAge] = useState("");

  return (
    <>
      <input
        value={firstName}
        onChange={(e) => setFirstName(e.target.value)}
      />
      <input
        value={lastName}
        onChange={(e) => setLastName(e.target.value)}
      />
      <input
        type="number"
        value={age}
        onChange={(e) => setAge(e.target.value)}
      />
    </>
  );
}
▶ Try it YourselfMultiple States

MultipleStates.jsx
import { useState } from 'react';

export function Form() {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [age, setAge] = useState("");

  return (
    <>
      <input
        value={firstName}
        onChange={(e) => setFirstName(e.target.value)}
        placeholder="First name"
      />
      <input
        value={lastName}
        onChange={(e) => setLastName(e.target.value)}
        placeholder="Last name"
      />
      <input
        type="number"
        value={age}
        onChange={(e) => setAge(e.target.value)}
        placeholder="Age"
      />
      <p>{firstName} {lastName}, {age}</p>
    </>
  );
}
6

Managing Array State

When state is an array, use the spread operator to create a new array instead of mutating.

array-operations.jsx
const [items, setItems] = useState(["Apple", "Banana"]);

// Add item
setItems([...items, "Orange"]);

// Remove item
setItems(items.filter(item => item !== "Apple"));

// Update item
setItems(items.map(item =>
  item === "Banana" ? "Grape" : item
));
▶ Try it YourselfArray State
Apple
Banana
Orange
ArrayState.jsx
import { useState } from 'react';

export function ItemList() {
  const [items, setItems] = useState(["Apple", "Banana"]);
  const [input, setInput] = useState("");

  const addItem = () => {
    setItems([...items, input]);
    setInput("");
  };

  const removeItem = (index) => {
    setItems(items.filter((_, i) => i !== index));
  };

  return (
    <>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />
      <button onClick={addItem}>Add</button>

      {items.map((item, i) => (
        <div key={i}>
          {item}
          <button onClick={() => removeItem(i)}>Remove</button>
        </div>
      ))}
    </>
  );
}
7

Managing Object State

When state is an object, use the spread operator to create a new object with updated properties.

object-operations.jsx
const [user, setUser] = useState({
  name: "Alice",
  email: "[email protected]",
  age: 25
});

// Update one property
setUser({ ...user, name: "Bob" });

// Update multiple properties
setUser({
  ...user,
  name: "Charlie",
  age: 30
});
▶ Try it YourselfObject State

Name: Alice

Email: [email protected]

Age: 25

ObjectState.jsx
import { useState } from 'react';

export function UserForm() {
  const [user, setUser] = useState({
    name: "Alice",
    email: "[email protected]",
    age: 25
  });

  const updateField = (field, value) => {
    setUser({ ...user, [field]: value });
  };

  return (
    <>
      <input
        value={user.name}
        onChange={(e) => updateField("name", e.target.value)}
      />
      <input
        value={user.email}
        onChange={(e) => updateField("email", e.target.value)}
      />
      <input
        type="number"
        value={user.age}
        onChange={(e) => updateField("age", +e.target.value)}
      />
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <p>Age: {user.age}</p>
    </>
  );
}
8

Functional Updates

Sometimes you need to calculate the new state based on the previous state. Use a function as the argument to the setter.

functional-update.jsx
const [count, setCount] = useState(0);

// ✅ GOOD: Use function for updates based on previous state
const increment = () => {
  setCount(prevCount => prevCount + 1);
};

// ❌ BAD: This might not work correctly
const badIncrement = () => {
  setCount(count + 1);  // count might be stale
};
Use functional updates when:
  • • Updating based on previous state
  • • Multiple state updates in quick succession
  • • Using state in event handlers
9

Best Practices

Always use setter function: Don't mutate state directly
Use descriptive names: setUserName not setX
Keep state simple: Use separate useState for different data
Use spread operator: Create new objects/arrays, don't mutate
Use functional updates: When depending on previous state
10

Common Mistakes

1Mistake: Mutating State Directly
mistake-mutate.jsx
// ❌ BAD: Mutating state directly
const [user, setUser] = useState({ name: "Alice" });
user.name = "Bob";  // Don't do this!
setUser(user);

// ✅ GOOD: Create new object with spread
setUser({ ...user, name: "Bob" });
2Mistake: Not Using Setter Function
mistake-no-setter.jsx
// ❌ BAD: Direct assignment
let count = 0;
count = count + 1;  // This won't trigger re-render!

// ✅ GOOD: Use setState hook
const [count, setCount] = useState(0);
setCount(count + 1);  // This triggers re-render
3Mistake: Mutating Arrays
mistake-array-mutate.jsx
// ❌ BAD: Array.push() mutates
const [items, setItems] = useState([1, 2, 3]);
items.push(4);  // Don't do this!
setItems(items);

// ✅ GOOD: Create new array with spread
setItems([...items, 4]);
11

What's Next?

🚀 Next Topics to Learn:
  • useEffect: Handle side effects and API calls
  • useReducer: Manage complex state with actions
  • useContext: Avoid prop drilling with global state
  • Custom Hooks: Create reusable state logic
  • Form Libraries: React Hook Form, Formik for complex forms

Practice with the examples above! 💪 Try modifying the code and running the examples. Build simple apps like counters, todo lists, and forms to master useState!

About the Author

TG

Thirdy Gayares

Passionate developer creating custom solutions for everyone. I specialize in building user-friendly tools that solve real-world problems while maintaining the highest standards of security and privacy.