Reactive Data — useState
useState is the most important Hook in React. In this chapter, you will understand React's state management mechanism and implement category filtering.
How is React's "reactivity" different from Vue3?
Vue3 uses ref/reactive to create reactive data; when the data changes, the view updates automatically.
React's approach is different:When you modify state, React re-executes the component function with the new state and renders a new view。
This is the origin of React's name — components "react" to changes in state.
Basic usage of useState
useStateIt is React's most basic Hook, used to add state to function components.
Example
import { useState } from 'react'
function Counter() {
// useState(initial value) returns an array: [current value, update function]
const [count, setCount] = useState(0)
function handleClick() {
setCount(count + 1) // Update state, React will automatically re-render the component
}
return (
<div>
<p>Click count:{count}</p>
<button onClick={handleClick}>+1</button>
</div>
)
}
Every time the button is clicked,setCountthe value of count is updated, React re-executes the Counter function with the new value, returns new JSX, and the page updates automatically.
React convention: state names use
[something, setSomething]destructuring. Functions starting with set are a conventional naming practice, not a hard requirement, but the entire community writes it this way.
Why can't we directly modify state?
In React,state is read-only. It must be updated through a setter function; you cannot modify it directly.
Example
function ArticleManager() {
const [title, setTitle] = useState('Default Title')
// Wrong way: direct modification
// title = 'New Title' // React doesn't know you changed it, so it won't re-render
// Correct way: update through setter
function updateTitle() {
setTitle('New Title') // React knows the state changed and will re-render
}
return (
<div>
<p>{title}</p>
<button onClick={updateTitle}>Update Title</button>
</div>
)
}
Behind this is React'sImmutablePrinciple: don't modify old data, but replace it with new data.
The correct way to update arrays and objects
For arrays and objects, you cannot directly push, splice, or modify properties; you must create a new copy.
Example
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Write Blog', done: true }
])
// Add an item: spread the old array + new element
function addTodo(text) {
const newTodo = { id: Date.now(), text, done: false }
setTodos([...todos, newTodo]) // Use the spread operator to create a new array
}
// Modify an item: iterate with map, find the one to change, return a new object
function toggleTodo(id) {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
))
}
// Delete an item: use filter to remove the unwanted one
function removeTodo(id) {
setTodos(todos.filter(todo => todo.id !== id))
}
return (
<ul>
{todos.map(todo => (
<li key={todo.id} onClick={() => toggleTodo(todo.id)}>
{todo.done ? <s>{todo.text}</s> : todo.text}
</li>
))}
</ul>
)
}
| Operation | Wrong way | Correct way |
|---|---|---|
| Add | arr.push(item) | setArr([...arr, item]) |
| Modify | arr[0] = newVal | setArr(arr.map(el => el.id === id ? ... : el)) |
| Delete | arr.splice(i, 1) | setArr(arr.filter(el => el.id !== id)) |
| Modify object properties | obj.name = 'xxx' | setObj({ ...obj, name: 'xxx' }) |
Core principle: always replace the old with a new array/new object. The spread operator
...and the array methods you've learned (map, filter) are the best partners for immutable updates.
useMemo — Derived computed values
useMemoUsed to cache computation results. When dependencies haven't changed, skip recomputation to avoid unnecessary performance overhead.
Example
function ArticleFilter() {
const [articles] = useState([
{ id: 1, title: 'Getting Started with React', category: 'React' },
{ id: 2, title: 'Promise Explained', category: 'JavaScript' },
{ id: 3, title: 'Grid Layout', category: 'CSS' },
{ id: 4, title: 'React Hooks', category: 'React' }
])
const [activeCategory, setActiveCategory] = useState('All')
// useMemo caches the filtering result; it only recomputes when dependencies change
const filteredArticles = useMemo(() => {
console.log('Recalculate filter results') // Verify the caching effect
if (activeCategory === 'All') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory]) // Dependency array
// Use useMemo to compute statistics
const totalCount = useMemo(() => articles.length, [articles])
const filteredCount = useMemo(() => filteredArticles.length, [filteredArticles])
return (
<div>
<p>common{totalCount}articles, currently{filteredCount}article</p>
<button onClick={() => setActiveCategory('All')}>All</button>
<button onClick={() => setActiveCategory('React')}>React</button>
<button onClick={() => setActiveCategory('JavaScript')}>JavaScript</button>
<button onClick={() => setActiveCategory('CSS')}>CSS</button>
{filteredArticles.map(a => (
<div key={a.id}><h3>{a.title}</h3><span>{a.category}</span></div>
))}
</div>
)
}
The dependency array of useMemo(fn, deps) must be filled in completely: all component internal variables used in the function should be declared in the array. React decides whether to recompute via shallow comparison.
Hands-on: Add category filtering to the blog
Example
import { useState, useMemo } from 'react'
import './App.css'
function App() {
const [articles] = useState([
{ id: 1, title: 'The Complete Guide to Getting Started with React', summary: 'Learn React from Scratch', category: 'React', date: '2024-05-10' },
{ id: 2, title: 'JS Asynchronous Programming Explained', summary: 'Understand Promise and async/await', category: 'JavaScript', date: '2024-05-08' },
{ id: 3, title: 'CSS Grid Layout in Action', summary: 'Implementing Responsive Layouts with Grid', category: 'CSS', date: '2024-05-05' },
{ id: 4, title: 'React Hooks in Depth', summary: 'In-Depth Understanding of useState and useEffect', category: 'React', date: '2024-05-03' },
{ id: 5, title: 'The Complete Guide to Flexbox', summary: 'Learn Flex Layout in One Article', category: 'CSS', date: '2024-05-01' },
])
const [activeCategory, setActiveCategory] = useState('All')
// Extract all categories (deduplicate)
const categories = useMemo(() => {
const cats = articles.map(a => a.category)
return ['All', ...new Set(cats)]
}, [articles])
// Filter articles by category
const filteredArticles = useMemo(() => {
if (activeCategory === 'All') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory])
return (
<div className="app">
<header className="navbar">
<h1 className="logo">EXAMPLE Blog</h1>
<nav>
<a href="/">Home</a>
<a href="#">About</a>
</nav>
</header>
<main className="container">
<h2 className="section-title">Latest Articles</h2>
{/* Category filter button group */}
<div className="category-bar">
{categories.map(cat => (
<button
key={cat}
className={activeCategory === cat ? 'active' : ''}
onClick={() => setActiveCategory(cat)}
>
{cat}
</button>
))}
</div>
<p className="result-info">common{filteredArticles.length}article</p>
{filteredArticles.length === 0 ? (
<p className="empty-tip">No articles under this category yet</p>
) : (
<div className="article-grid">
{filteredArticles.map(article => (
<div key={article.id} className="article-card">
<div className="card-content">
<span className="card-category">{article.category}</span>
<h3>{article.title}</h3>
<p>{article.summary}</p>
<span className="card-date">{article.date}</span>
</div>
</div>
))}
</div>
)}
</main>
<footer className="footer">
<p>© 2024 EXAMPLE Blog. Powered by React.</p>
</footer>
</div>
)
}
export default App
Example
.category-bar {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.category-bar button {
padding: 6px 16px;
border: 1px solid #ddd;
border-radius: 20px;
background: #fff;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.category-bar button.active {
background: #1976d2;
color: #fff;
border-color: #1976d2;
}
.category-bar button:hover {
border-color: #1976d2;
}
.result-info {
color: #999;
font-size: 14px;
margin-bottom: 16px;
}
Chapter Summary
In this chapter, you mastered the three core concepts of React state management: useState for creating and updating state, the immutable update principle (spread operator + map/filter), and useMemo for caching derived computed values.
Through the category filtering feature, you experienced React's data-driven pattern of "setState → re-render → new view".
Other Extensions