React: State Basics: useState

Last updated: 2026-08-26

State is a component’s memory. If Props are parameters given to you by others (read-only), then State is your own little notebook (editable). useState is the “memory chip” that React installs in a component.


1. What You'll Learn



2. Reflections Triggered by a Counter

(1) Pain Point: Regular variables do not trigger UI updates

Bob wants to make a simple counter: when you click the button, the number increases by 1.

JSX
// ❌ Ordinary Variables,Will not trigger a re-render
function Counter() {
  let count = 0  // Ordinary Variables

  function handleClick() {
    count = count + 1  // Variable changed, but UI won't update!
    console.log(count) // Console Display 1, 2, 3... But the page always displays 0
  }

  return (
    <div>
      <p>Count:{count}</p>  {/* Always Show 0 */}
      <button onClick={handleClick}>+1</button>
    </div>
  )
}
▶ Try it Yourself

Bob discovered a problem: Ordinary variables do not notify React to update the UI. Every time the component re-renders, count is reset to 0.

(2) A Solution Using useState

JSX
// ✅ Usage State,React It automatically tracks changes and updates UI
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)  // Initial value 0

  function handleClick() {
    setCount(count + 1)  // Update Status → React Automatic Re-rendering
  }

  return (
    <div>
      <p>Count:{count}</p>  {/* Automatically update with each click */}
      <button onClick={handleClick}>+1</button>
    </div>
  )
}
▶ Try it Yourself

Return Value: useState returns an array [current value, setter function]. After calling setCount, React automatically re-renders the component to display the latest values.



3. Core Concepts of useState

(1) Deconstructing Assignment

JSX
import { useState } from 'react'

// useState(initialValue) Back [value, setValue]
const [count, setCount] = useState(0)
//      ^      ^          ^
//      |      |          └─ Initial value(Applies only to the first render)
//      |      └─ Update Function(Triggers a re-render after being called)
//      └─ Current status value(Get the latest value on every render)
▶ Try it Yourself
Section Description
count Current state value; the latest value is retrieved with each render
setCount Update function: Calling it → React re-renders the component
useState(0) Initial value (used during the first render; ignored in subsequent renders)

(2) Update Mechanism

JSX
function Example() {
  const [count, setCount] = useState(0)

  function handleClick() {
    setCount(count + 1)  // 1. Request Update
    // Note:Here count Still the old value!
    console.log(count)    // Output 0,No 1
  }

  return <button onClick={handleClick}>{count}</button>
}
▶ Try it Yourself

Key Concept: setCount(count + 1) means "ask React to update count on the next render," not "immediately change count." This is called asynchronous updating.

(3) Functional Updates

If the new state depends on the old state, you should use functional updates:

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

  function handleClick() {
    // ✅ Recommendations:Functional Updates
    setCount(prev => prev + 1)
    setCount(prev => prev + 1)  // Called twice in a row, count becomes +2
  }

  // ❌ Not recommended:Update Directly(Chain calls take effect only once)
  function handleBadClick() {
    setCount(count + 1)
    setCount(count + 1)  // Called twice count All are old values,The result was only +1
  }

  return <button onClick={handleClick}>Currently:{count}</button>
}
▶ Try it Yourself
Method Syntax Characteristics
Update Directly setCount(count + 1) Simple, but data is lost with consecutive calls
Functional Update setCount(prev => prev + 1) Accurate; works correctly even with consecutive calls


4. Updating Arrays and Objects (Immutability)

React requires that state not be modified directly. Never use state.push() or state.name = 'xxx'; instead, create a new array or object to replace the old value.

(1) Objects to Update

JSX
function UserEditor() {
  const [user, setUser] = useState({
    name: 'Alice',
    age: 28,
    email: 'alice@example.com'
  })

  function updateName(newName) {
    // ❌ Error:Edit directly state Object
    user.name = newName
    // React Changes will not be detected,UI Will not be updated

    // ✅ Correct:Create a new object
    setUser({ ...user, name: newName })
  }

  function updateAge(newAge) {
    // ✅ Expand Operator:Keep the other fields,Update Only age
    setUser({ ...user, age: newAge })
  }

  function resetUser() {
    // ✅ Reset to default values
    setUser({ name: '', age: 0, email: '' })
  }

  return (
    <div>
      <p>{user.name} - {user.age} years old</p>
      <button onClick={() => updateName('Bob')}>Change Name Bob</button>
      <button onClick={() => updateAge(user.age + 1)}>Age +1</button>
    </div>
  )
}
▶ Try it Yourself

(2) Update the array

JSX
function ShoppingCart() {
  const [items, setItems] = useState([
    { id: 1, name: 'Apple', qty: 2 },
    { id: 2, name: 'Banana', qty: 1 }
  ])

  // Add:Create a new array using the spread operator
  function addItem(name) {
    setItems([...items, { id: Date.now(), name, qty: 1 }])
  }

  // Delete:use  filter Create a new array
  function removeItem(id) {
    setItems(items.filter(item => item.id !== id))
  }

  // Update:use  map Create a new array
  function updateQty(id, newQty) {
    setItems(items.map(item =>
      item.id === id ? { ...item, qty: newQty } : item
    ))
  }

  return (
    <div>
      <button onClick={() => addItem('Orange')}>Add orange</button>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.name} × {item.qty}
            <button onClick={() => updateQty(item.id, item.qty + 1)}>+</button>
            <button onClick={() => removeItem(item.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  )
}
▶ Try it Yourself

▶ Example: Array Operations Quick Reference Table

Output:

TEXT 📖 Display only
State: arr
JSX
// ============================================
// Example:React Array Operations in C 6 Common Scenarios
// ============================================

const [arr, setArr] = useState([1, 2, 3])

// 1. Append to the end
setArr([...arr, 4])           // [1, 2, 3, 4]

// 2. Add to the beginning
setArr([0, ...arr])           // [0, 1, 2, 3]

// 3. Insert in the middle
const insertAt = 1
setArr([...arr.slice(0, insertAt), 99, ...arr.slice(insertAt)])
                              // [1, 99, 2, 3]

// 4. Delete Element(filter)
setArr(arr.filter(n => n !== 2))  // [1, 3]

// 5. Update Element(map)
setArr(arr.map(n => n === 2 ? 22 : n))  // [1, 22, 3]

// 6. Sort(Copy first, then sort)
setArr([...arr].sort((a, b) => b - a))  // [3, 2, 1]
▶ Try it Yourself

Output:

TEXT 📖 Display only
Array state operations: push → add item, filter → remove item, map → update item. Each returns a new array reference for React re-rendering


5. Multiple State Variables

A component can have multiple state variables. It is recommended to break them down by logic rather than cramming them into a single large object:

JSX
function RegistrationForm() {
  // ✅ Recommendations:Logical Breakdown
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)
  const [errors, setErrors] = useState({})

  // ❌ Not recommended:All states are stored in a single object
  const [form, setForm] = useState({
    name: '', email: '', password: '',
    isSubmitting: false, errors: {}
  })
  // Expand the entire object when updating,Fields that are easy to overlook
}
▶ Try it Yourself
Strategy Advantages Disadvantages
Split into multiple useStates Precise updates, type-safe, and easy to read Too many variables when there's a lot of state
Combined into a single useState Convenient for updating multiple fields at once The update syntax is cumbersome and it's easy to forget to expand it

Recommendation: Use multiple useState instances for independent states (such as input fields, loading indicators, and error messages); logically related fields (such as all fields in a user profile) can be combined into a single objeto.



6. Lifting State Up

When multiple components need to share the same state, lift the state up to their nearest common parent component.

100%
graph TB
    subgraph "Before the status upgrade(Data is not shared)"
        A[Parent Component App] --- B[Child component A<br/>Have their own count]
        A --- C[Child component B<br/>Have their own count]
    end
    
    subgraph "After the status upgrade(Data Sharing)"
        D[Parent Component App<br/>**count Here**] --- E[Child component A<br/>Read count, Call setCount]
        D --- F[Child component B<br/>Read count, Call setCount]
    end

▶ Example: Practical Application of State Promotion

Output:

TEXT 📖 Display only
State: arr (setter: setArr)
JSX
// ============================================
// Example:Temperature Converter(Status Upgrade)
// Features:Converting Between Celsius and Fahrenheit,Share the same temperature value
// ============================================

// ---- Child component:Celsius Temperature Input ----
function CelsiusInput({ celsius, onCelsiusChange }) {
  return (
    <div>
      <label>Celsius(°C):</label>
      <input
        value={celsius}
        onChange={e => onCelsiusChange(e.target.value)}
        style={{ margin: '8px', padding: '4px' }}
      />
    </div>
  )
}

// ---- Child component:Fahrenheit Temperature Input ----
function FahrenheitInput({ fahrenheit, onFahrenheitChange }) {
  return (
    <div>
      <label>Fahrenheit(°F):</label>
      <input
        value={fahrenheit}
        onChange={e => onFahrenheitChange(e.target.value)}
        style={{ margin: '8px', padding: '4px' }}
      />
    </div>
  )
}

// ---- Parent Component:Status is managed here ----
function TemperatureConverter() {
  // State Propagated to the Common Parent Component
  const [temperature, setTemperature] = useState('')

  function handleCelsiusChange(value) {
    setTemperature(value)  // Store at Celsius
    // There is no need for two states!Keep only one,Another one calculated using a formula
  }

  function handleFahrenheitChange(value) {
    // Fahrenheit → Celsius:°C = (°F - 32) × 5/9
    setTemperature(value ? ((parseFloat(value) - 32) * 5 / 9).toFixed(1) : '')
  }

  const celsius = temperature
  const fahrenheit = temperature
    ? (parseFloat(temperature) * 9 / 5 + 32).toFixed(1)
    : ''

  return (
    <div style={{ padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
      <h2>Temperature Converter</h2>
      <CelsiusInput celsius={celsius} onCelsiusChange={handleCelsiusChange} />
      <FahrenheitInput fahrenheit={fahrenheit} onFahrenheitChange={handleFahrenheitChange} />
      {temperature && (
        <p style={{ color: '#666', marginTop: '12px' }}>
          {celsius}°C = {fahrenheit}°F
        </p>
      )}
    </div>
  )
}
// Enter in the Celsius input field 100 → Automatic Display in Fahrenheit 212°F
// Enter in the Fahrenheit input field 212 → Automatic Celsius Display 100°C

Output:

TEXT 📖 Display only
Enter 100 in Celsius → Fahrenheit auto-shows 212.0. Displays "100°C = 212.0°F". Two inputs stay synchronized.


7. Complete Example: Shopping Cart Counter

JSX
// ============================================
// Complete Example:Shopping Cart(useState Comprehensive Application)
// Features:Add Item、Increase or Decrease Quantity、Delete、Total Price Calculation
// ============================================

import { useState } from 'react'

function ShoppingCart() {
  // Several State Variable,Logical Breakdown
  const [items, setItems] = useState([
    { id: 1, name: 'React Hands-On Tutorials', price: 89, qty: 1 },
    { id: 2, name: 'TypeScript Getting Started', price: 59, qty: 2 }
  ])
  const [discountCode, setDiscountCode] = useState('')
  const [appliedDiscount, setAppliedDiscount] = useState(0)

  // Increase the quantity(Functional updates ensure accuracy)
  function increment(id) {
    setItems(items.map(item =>
      item.id === id ? { ...item, qty: item.qty + 1 } : item
    ))
  }

  // Decrease the quantity(No less than 1)
  function decrement(id) {
    setItems(items.map(item =>
      item.id === id ? { ...item, qty: Math.max(1, item.qty - 1) } : item
    ))
  }

  // Delete Product
  function remove(id) {
    setItems(items.filter(item => item.id !== id))
  }

  // Apply a discount code
  function applyDiscount() {
    if (discountCode === 'REACT2026') {
      setAppliedDiscount(20)  // over 100 minus 20
    } else {
      alert('The discount code is invalid.')
    }
  }

  // Calculate the Total Price
  const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0)
  const total = Math.max(0, subtotal - appliedDiscount)

  return (
    <div style={{ maxWidth: '600px', margin: '0 auto' }}>
      <h2>🛒 Shopping Cart</h2>

      {/* Product List */}
      {items.length === 0 ? (
        <p style={{ color: '#999', textAlign: 'center', padding: '40px' }}>
          Your shopping cart is empty,Go check it out!!
        </p>
      ) : (
        items.map(item => (
          <div key={item.id} style={{
            display: 'flex', alignItems: 'center',
            padding: '12px', borderBottom: '1px solid #f0f0f0'
          }}>
            <div style={{ flex: 1 }}>
              <h4 style={{ margin: 0 }}>{item.name}</h4>
              <p style={{ margin: '4px 0', color: '#ff4d4f' }}>${item.price}</p>
            </div>
            
            {/* Quantity Control */}
            <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
              <button onClick={() => decrement(item.id)} style={btnStyle}>−</button>
              <span>{item.qty}</span>
              <button onClick={() => increment(item.id)} style={btnStyle}>+</button>
            </div>

            {/* Subtotal */}
            <p style={{ margin: '0 16px', fontWeight: 'bold', width: '80px', textAlign: 'right' }}>
              ${item.price * item.qty}
            </p>

            {/* Delete */}
            <button onClick={() => remove(item.id)} style={{ ...btnStyle, backgroundColor: '#ff4d4f', color: 'white' }}>
              ✕
            </button>
          </div>
        ))
      )}

      {/* Discount Code */}
      <div style={{ marginTop: '16px', display: 'flex', gap: '8px' }}>
        <input
          value={discountCode}
          onChange={e => setDiscountCode(e.target.value)}
          placeholder="Enter the discount code"
          style={{ flex: 1, padding: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }}
        />
        <button onClick={applyDiscount} style={{
          padding: '8px 16px', backgroundColor: '#52c41a', color: 'white',
          border: 'none', borderRadius: '4px', cursor: 'pointer'
        }}>
          Applications
        </button>
      </div>

      {/* Price Summary */}
      <div style={{ marginTop: '16px', padding: '16px', backgroundColor: '#fafafa', borderRadius: '8px' }}>
        <p>Subtotal:${subtotal}</p>
        {appliedDiscount > 0 && <p style={{ color: '#52c41a' }}>Discount: -${appliedDiscount}</p>}
        <p style={{ fontSize: '20px', fontWeight: 'bold' }}>Total:${total}</p>
      </div>
    </div>
  )
}

const btnStyle = {
  width: '32px', height: '32px',
  border: '1px solid #d9d9d9', borderRadius: '4px',
  backgroundColor: 'white', cursor: 'pointer',
  fontSize: '16px', display: 'flex', alignItems: 'center',
  justifyContent: 'center'
}

Interaction Flow:

  • Tap +/− to adjust the quantity; the subtotal and total are updated in real time
  • Click ✕ to delete an item; display a friendly prompt when the shopping cart is empty
  • Enter the promo code REACT2026 to save 20 yuan

▶ Example 3: Updating an Object's State—Editing User Information

Output:

TEXT 📖 Display only
Subheading: "Temperature Converter". Displays: "Celsius(°C):". State: temperature (setter: setTemperature)
JSX
function UserEditor() {
  const [user, setUser] = useState({ name: 'Alice', email: 'alice@test.com', age: 28 })

  function updateField(field, value) {
    setUser(prev => ({ ...prev, [field]: value }))
  }

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Edit Profile</h3>
      <input value={user.name} onChange={e => updateField('name', e.target.value)}
        placeholder="Name" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <input value={user.email} onChange={e => updateField('email', e.target.value)}
        placeholder="Email" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <input type="number" value={user.age} onChange={e => updateField('age', Number(e.target.value))}
        placeholder="Age" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4, fontSize: 13 }}>
        {JSON.stringify(user, null, 2)}
      </pre>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
State: address. Inputs: Street, City, ZIP

▶ Example 4: Nested Object States—Address Management

Output:

TEXT 📖 Display only
Displays: "Edit Profile". State: user (setter: setUser). Input: Name, Email, Age
JSX
function AddressForm() {
  const [address, setAddress] = useState({
    street: '', city: '', zip: '',
    country: 'US', isPrimary: true,
  })

  function update(path, value) {
    setAddress(prev => ({ ...prev, [path]: value }))
  }

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Shipping Address</h3>
      <input value={address.street} onChange={e => update('street', e.target.value)}
        placeholder="Street" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
        <input value={address.city} onChange={e => update('city', e.target.value)}
          placeholder="City" style={{ flex: 2, padding: 8, borderRadius: 4 }} />
        <input value={address.zip} onChange={e => update('zip', e.target.value)}
          placeholder="ZIP" style={{ flex: 1, padding: 8, borderRadius: 4 }} />
      </div>
      <select value={address.country} onChange={e => update('country', e.target.value)}
        style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }}>
        <option value="US">United States</option>
        <option value="CN">China</option>
        <option value="JP">Japan</option>
      </select>
      <label>
        <input type="checkbox" checked={address.isPrimary}
          onChange={e => update('isPrimary', e.target.checked)} /> Primary address
      </label>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Shipping form: Street, City, ZIP, Country dropdown (US/CN/JP), "Primary address" checkbox. All fields update state reactively.

▶ Example 5: Functional Updates Solve the Problem of Batch Updates

Output:

TEXT 📖 Display only
Displays: "Shipping Address". State: address (setter: setAddress). Input: Street, City, ZIP. Dropdown: United States, China, Japan
JSX
function ScoreBoard() {
  const [score, setScore] = useState(0)
  const [multiplier, setMultiplier] = useState(1)

  function addPoints(base) {
    setScore(prev => prev + base * multiplier)
  }

  function resetScore() {
    setScore(0)
    setMultiplier(1)
  }

  function doubleMultiplier() {
    setMultiplier(prev => Math.min(prev * 2, 8))
  }

  return (
    <div style={{ maxWidth: 300, margin: '0 auto', textAlign: 'center' }}>
      <h3>Score: {score}</h3>
      <p>Multiplier: x{multiplier}</p>
      <div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginBottom: 8 }}>
        <button onClick={() => addPoints(10)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+10 pts</button>
        <button onClick={() => addPoints(50)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+50 pts</button>
        <button onClick={() => addPoints(100)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+100 pts</button>
      </div>
      <div style={{ display: 'flex', gap: 8, justifyContent: 'center' }}>
        <button onClick={doubleMultiplier} style={{ padding: '8px 16px', cursor: 'pointer' }}>2x Multiplier</button>
        <button onClick={resetScore} style={{ padding: '8px 16px', cursor: 'pointer' }}>Reset</button>
      </div>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Score: 0, Multiplier: x1. +10/+50/+100 add points×multiplier. "2x Multiplier" doubles (max x8). Reset → Score: 0, x1

❓ FAQ

Q What is the performance difference between useState and regular variables?
A useState incurs additional overhead (tracking changes, scheduling rendering), so it should only be used in scenarios where "UI updates are triggered when changes occur." If a variable changes but does not require a UI update (such as a timer ID or scroll position), using useRef (Lesson 10) is more appropriate.
Q How many useStates can there be in a single component? Are there any performance issues?
A There is no limit on the number. The official React documentation recommends breaking things down into multiple useStates based on logic rather than using a single large object. Each useState call is a "state unit," and React can efficiently handle a large number of useStates. Common components typically have 3–8 useStates.
Q Is a setState update synchronous or asynchronous?
A Prior to React 18, setState was "automatically batched" within event handlers, but synchronous within setTimeout and Promise calls. React 18 introduced Automatic Batching, so setState is now batch-asynchronous regardless of whether it’s called within an event handler, setTimeout, Promise, or fetch callback. If you absolutely need to access the updated DOM synchronously, you can use flushSync(() => setState(...)).

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty ⭐): Create a LikeButton component that toggles between ❤️ and 🤍 states when clicked and displays the number of likes.
  2. Advanced Exercise (Difficulty ⭐⭐): Create a ExpenseTracker component that uses useState to manage a list of expenses (description, amount, date), and supports adding and removing items.
  3. Challenge (Difficulty: ⭐⭐⭐): Create a ColorPicker component that uses three useState instances to manage the R, G, and B slider values (0–255), and displays a real-time color preview and hex color code.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏