Practical useState Examples

Introduction

React is a JavaScript library for building interactive user interfaces. One of the key features that makes React powerful is its state management capability. State allows components to store and manage dynamic data that affects the rendering of the UI.

In React functional components, the useState hook is the primary way to add and manage state. It provides a simple and intuitive API for storing values, updating them, and ensuring the UI reflects these changes automatically.

This post explores practical examples of useState in real-world scenarios. We will cover:

  1. Toggle components
  2. Counter applications
  3. Form input handling
  4. Managing arrays and objects in state

These examples will help you understand how to use useState effectively and write dynamic, responsive components.


What is useState?

useState is a React hook that lets you add state to functional components. It returns an array with two elements:

  1. The current state value
  2. A function to update the state

Syntax

const [state, setState] = useState(initialValue);
  • state: Holds the current value of the state.
  • setState: Updates the state and triggers a re-render.
  • initialValue: The initial value of the state.

1. Toggle Component

A toggle component is a common UI element that switches between two states, such as ON/OFF, Dark/Light mode, or Show/Hide content.

Example: Toggle Button

import React, { useState } from "react";

function ToggleButton() {
  const [isOn, setIsOn] = useState(false);

  const handleToggle = () => {
setIsOn(!isOn);
}; return (
<div>
  <button onClick={handleToggle}>
    {isOn ? "ON" : "OFF"}
  </button>
  <p>The switch is {isOn ? "ON" : "OFF"}</p>
</div>
); } export default ToggleButton;

Explanation

  • isOn stores the current toggle state.
  • setIsOn updates the state whenever the button is clicked.
  • The UI updates automatically based on the state value.

Variations

  • Toggle Dark/Light Mode: Change styles dynamically based on state.
  • Show/Hide Content: Conditionally render sections using state.

2. Counter Application

Counters are another practical example to demonstrate useState. They often include increment, decrement, and reset functionality.

Example: Simple Counter

import React, { useState } from "react";

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

  return (
<div>
  <h1>Count: {count}</h1>
  <button onClick={() => setCount(count + 1)}>Increment</button>
  <button onClick={() => setCount(count - 1)}>Decrement</button>
  <button onClick={() => setCount(0)}>Reset</button>
</div>
); } export default Counter;

Explanation

  • count stores the current count.
  • setCount updates the count value.
  • Clicking the buttons triggers state changes and re-renders the component.

Advanced Counter: Step Value

function CounterWithStep() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);

  return (
<div>
  <h1>Count: {count}</h1>
  <input
    type="number"
    value={step}
    onChange={(e) => setStep(Number(e.target.value))}
  />
  <button onClick={() => setCount(count + step)}>Increment</button>
  <button onClick={() => setCount(count - step)}>Decrement</button>
</div>
); }
  • Allows dynamic step increments based on user input.

3. Form Input Handling

Forms are essential in most applications. Managing form input using useState is straightforward and ensures controlled components.

Example: Single Input Field

import React, { useState } from "react";

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

  const handleChange = (e) => {
setName(e.target.value);
}; const handleSubmit = (e) => {
e.preventDefault();
alert(Submitted Name: ${name});
setName(""); // Clear input after submit
}; return (
<form onSubmit={handleSubmit}>
  <input
    type="text"
    value={name}
    onChange={handleChange}
    placeholder="Enter your name"
  />
  <button type="submit">Submit</button>
</form>
); } export default NameForm;

Explanation

  • value of the input is controlled by name state.
  • setName updates the state whenever the input changes.
  • Submitting the form triggers a function that can handle the data.

Example: Multiple Input Fields

function UserForm() {
  const [formData, setFormData] = useState({ username: "", email: "" });

  const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
}; const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
}; return (
<form onSubmit={handleSubmit}>
  <input
    type="text"
    name="username"
    value={formData.username}
    onChange={handleChange}
    placeholder="Username"
  />
  <input
    type="email"
    name="email"
    value={formData.email}
    onChange={handleChange}
    placeholder="Email"
  />
  <button type="submit">Submit</button>
</form>
); }
  • formData is an object storing multiple input fields.
  • Spread operator ensures previous fields are not overwritten.

4. Managing Arrays and Objects in State

useState is not limited to primitive values. You can store arrays and objects, which is useful for managing lists, complex data, and dynamic content.


Example: Managing an Array of Items

import React, { useState } from "react";

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

  const addItem = () => {
setItems([...items, "Orange"]); // Add a new item
}; const removeItem = (index) => {
setItems(items.filter((_, i) => i !== index));
}; return (
<div>
  <ul>
    {items.map((item, index) => (
      <li key={index}>
        {item} <button onClick={() => removeItem(index)}>Remove</button>
      </li>
    ))}
  </ul>
  <button onClick={addItem}>Add Item</button>
</div>
); } export default ItemList;
  • Spread operator ...items ensures immutability.
  • Removing items creates a new array without mutating the original state.

Example: Managing Objects in State

function Profile() {
  const [profile, setProfile] = useState({ name: "Alice", age: 25 });

  const incrementAge = () => {
setProfile({ ...profile, age: profile.age + 1 });
}; const changeName = (newName) => {
setProfile({ ...profile, name: newName });
}; return (
<div>
  <p>Name: {profile.name}</p>
  <p>Age: {profile.age}</p>
  <button onClick={incrementAge}>Increase Age</button>
  <button onClick={() => changeName("Bob")}>Change Name</button>
</div>
); }
  • Always spread existing properties to avoid overwriting.
  • Each setProfile call creates a new object, triggering a re-render.

Example: Dynamic Object Updates

function SettingsForm() {
  const [settings, setSettings] = useState({
darkMode: false,
notifications: true,
}); const toggleSetting = (key) => {
setSettings({ ...settings, [key]: !settings[key] });
}; return (
<div>
  <p>Dark Mode: {settings.darkMode ? "ON" : "OFF"}</p>
  <p>Notifications: {settings.notifications ? "ON" : "OFF"}</p>
  <button onClick={() => toggleSetting("darkMode")}>Toggle Dark Mode</button>
  <button onClick={() => toggleSetting("notifications")}>Toggle Notifications</button>
</div>
); }
  • Dynamic property names [key] allow toggling multiple settings efficiently.

Best Practices for useState

  1. Use separate states for unrelated values: const [count, setCount] = useState(0); const [text, setText] = useState("");
    • Makes code clearer and avoids unnecessary re-renders.
  2. Never mutate state directly:
    • Always return a new object or array.
    • Example: setItems([...items, newItem])
  3. Lazy initialization for expensive states: const [data, setData] = useState(() => computeExpensiveValue());
  4. Use functional updates when state depends on previous value: setCount(prevCount => prevCount + 1);
  5. Keep state minimal:
    • Store only what is necessary for rendering.
    • Derive other values from existing state or props.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *