REACT Lesson 15 – useState Hook | Dataplexa
LESSON 15

useState Hook

Build interactive DataFlow components that respond to clicks, update counters, and manage form inputs with React's most essential hook.

Think of useState like a memory box for your components. Regular JavaScript variables forget their values when functions run again. But useState remembers. Every time someone clicks a button on Netflix or types in Facebook's search box, useState is working behind the scenes. The hook stores the current value and provides a function to update it.

What useState Actually Does

Regular JavaScript functions run from top to bottom, then disappear. Variables die. But React components need to remember things between renders.
// Regular JavaScript - count disappears every time
function regularCounter() {
  let count = 0; // Always starts at 0
  count = count + 1;
  console.log(count); // Always prints 1
}
React components work differently. They re-run when something changes, but useState preserves values across those re-runs.
// React with useState - count persists
import { useState } from 'react';

function DataFlowCounter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Revenue updates: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Update Revenue
      </button>
    </div>
  );
}
DataFlow Dashboard
What just happened?

Click the button multiple times. The count increases and stays increased. React preserves the state value between renders. Each click triggers a re-render with the new count value. Try this: Open browser dev tools and watch the component re-render on each click.

useState Anatomy

The hook follows a specific pattern. You destructure an array with exactly two elements: current value and setter function.

useState(initialValue)

Returns array with current state and setter function

Array Destructuring

[value, setValue] extracts both elements at once

// DataFlow user management
import { useState } from 'react';

function UserCounter() {
  // Destructure the array useState returns
  const [userCount, setUserCount] = useState(1247);
  const [isOnline, setIsOnline] = useState(true);
  const [userName, setUserName] = useState('Admin');
  
  return (
    <div>
      <h3>DataFlow Users</h3>
      <p>Total Users: {userCount}</p>
      <p>Status: {isOnline ? 'Online' : 'Offline'}</p>
      <p>Current User: {userName}</p>
    </div>
  );
}
DataFlow Dashboard

Hook Flow Diagram

Understanding when useState triggers re-renders is crucial. Every setState call causes the component to run again with new values.
Initial
State
First
Render
User
Action
setState
Called
Re-render
with New
The DataFlow team needs a revenue KPI card that updates when new sales data arrives. Watch how state flows through the component lifecycle.
// DataFlow revenue KPI with state flow
import { useState } from 'react';

function RevenueKPI() {
  const [revenue, setRevenue] = useState(245670);
  const [isLoading, setIsLoading] = useState(false);
  
  const updateRevenue = () => {
    setIsLoading(true); // First state change
    
    // Simulate API call
    setTimeout(() => {
      setRevenue(revenue + Math.floor(Math.random() * 10000));
      setIsLoading(false); // Second state change
    }, 1000);
  };
  
  return (
    <div style={{padding: '20px', border: '1px solid #e5e7eb'}}>
      <h3>Total Revenue</h3>
      <p style={{fontSize: '24px', fontWeight: 'bold'}}>
        ${revenue.toLocaleString()}
      </p>
      <button onClick={updateRevenue} disabled={isLoading}>
        {isLoading ? 'Updating...' : 'Refresh Data'}
      </button>
    </div>
  );
}
DataFlow Dashboard
What just happened?

Click "Refresh Data" and watch the button text change immediately, then the revenue updates after 1 second. Two separate setState calls triggered two re-renders. React batches state updates when they happen close together for performance. Try this: Click the button multiple times quickly and see how React handles the updates.

State Types and Initial Values

useState works with any JavaScript data type. The initial value sets both the type and starting value for your state.
// DataFlow dashboard with different state types
import { useState } from 'react';

function DashboardStats() {
  // Numbers for metrics
  const [users, setUsers] = useState(1247);
  const [revenue, setRevenue] = useState(89432.50);
  
  // Strings for text
  const [status, setStatus] = useState('Online');
  const [lastUpdate, setLastUpdate] = useState('2 minutes ago');
  
  // Booleans for toggles
  const [isVisible, setIsVisible] = useState(true);
  const [hasNotifications, setHasNotifications] = useState(false);
  
  // Arrays for lists
  const [alerts, setAlerts] = useState(['Server healthy', 'Backup complete']);
  
  // Objects for complex data
  const [settings, setSettings] = useState({
    theme: 'dark',
    notifications: true,
    autoRefresh: 30
  });
  
  return (
    <div>
      <h3>DataFlow Stats</h3>
      <p>Users: {users} | Revenue: ${revenue}</p>
      <p>Status: {status} | Updated: {lastUpdate}</p>
      <p>Visible: {isVisible ? 'Yes' : 'No'}</p>
      <p>Alerts: {alerts.length} items</p>
      <p>Theme: {settings.theme}</p>
    </div>
  );
}
DataFlow Dashboard

Updating State Correctly

State updates are asynchronous. React batches them for performance. Never mutate state directly — always use the setter function.
Common Mistake

Don't modify state directly. React won't detect the change and won't re-render the component.

// DataFlow filter component - right and wrong ways
import { useState } from 'react';

function DataFilter() {
  const [filters, setFilters] = useState(['date', 'category']);
  
  // ❌ WRONG - mutates state directly
  const addFilterWrong = () => {
    filters.push('price'); // React won't detect this change
    console.log(filters); // Array shows new item but UI won't update
  };
  
  // ✅ CORRECT - creates new array
  const addFilterCorrect = () => {
    setFilters([...filters, 'price']); // Spread creates new array
  };
  
  // ✅ CORRECT - using previous state
  const removeFilter = () => {
    setFilters(prevFilters => prevFilters.slice(0, -1));
  };
  
  return (
    <div>
      <h3>Active Filters ({filters.length})</h3>
      <p>{filters.join(', ')}</p>
      <button onClick={addFilterCorrect}>Add Filter</button>
      <button onClick={removeFilter}>Remove Filter</button>
    </div>
  );
}
DataFlow Dashboard

Form Inputs with useState

Forms are useState's bread and butter. Each input needs its own state variable, and onChange handlers update that state on every keystroke.
// DataFlow user settings form
import { useState } from 'react';

function UserSettings() {
  const [email, setEmail] = useState('admin@dataflow.com');
  const [notifications, setNotifications] = useState(true);
  const [theme, setTheme] = useState('dark');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Settings:', { email, notifications, theme });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <h3>DataFlow Settings</h3>
      
      <div>
        <label>Email:</label>
        <input 
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </div>
      
      <div>
        <label>
          <input
            type="checkbox"
            checked={notifications}
            onChange={(e) => setNotifications(e.target.checked)}
          />
          Enable Notifications
        </label>
      </div>
      
      <div>
        <label>Theme:</label>
        <select 
          value={theme} 
          onChange={(e) => setTheme(e.target.value)}
        >
          <option value="light">Light</option>
          <option value="dark">Dark</option>
          <option value="auto">Auto</option>
        </select>
      </div>
      
      <button type="submit">Save Settings</button>
    </form>
  );
}
DataFlow Dashboard
What just happened?

Type in the email field and watch it update instantly. Check the notification box or change the theme — all values sync immediately because each input's onChange handler calls the corresponding setter. The form shows current state values in real-time. Try this: Submit the form and check the browser console to see the state object logged.

Performance Note

useState re-renders the entire component on every state change. For complex forms with many inputs, consider techniques like debouncing or useCallback to optimize performance. But start simple — premature optimization is the root of all evil.

useState is React's foundation. Master it before moving to other hooks. Every interactive component you build will use useState in some form. The pattern stays consistent: destructure the array, use the current value, call the setter to update. Companies like Vercel and Linear built their entire dashboards using useState for local component state. Simple, predictable, and powerful when you understand the rules.

Quiz

1. The DataFlow team needs to track the number of active users. Which useState declaration correctly initializes a counter at zero?


2. What happens when you modify a useState array directly with push() instead of using the setter function?


3. In a DataFlow login form, which onChange handler correctly updates the email state when the user types in an input field?


Up Next: Custom Hooks

Extract useState logic into reusable custom hooks that multiple DataFlow components can share for cleaner, more maintainable code.