React
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 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>
);
}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>
);
}Hook Flow Diagram
Understanding when useState triggers re-renders is crucial. Every setState call causes the component to run again with new values.State
Render
Action
Called
with New
// 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>
);
}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>
);
}Updating State Correctly
State updates are asynchronous. React batches them for performance. Never mutate state directly — always use the setter function.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>
);
}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>
);
}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.
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.
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.