State is the engine of React interactivity. Without state, a React component is a static snapshot โ it receives props, renders JSX, and never changes. With state, a component becomes dynamic: it can remember information between renders, respond to user actions, and update the UI when that information changes. Understanding the difference between state and props โ and knowing which data belongs in each โ is the most important conceptual skill in React development. In this lesson you will build the mental model that drives every component design decision you will make in the MERN Blog.
State vs Props โ The Core Distinction
| Props | State | |
|---|---|---|
| Owned by | The parent that passes them | The component itself |
| Who can change it | Only the parent can change it | Only the component that owns it |
| Read-only inside the component? | Yes โ never mutate props | No โ updated via the setState setter |
| Causes re-render when changed? | Yes โ when parent re-renders with new props | Yes โ when setter is called |
| Initial value set by | The parent at call time | The component’s useState(initialValue) |
| Example | post.title passed from parent |
isLiked toggled by user click |
posts in state and a searchQuery in state, the filtered list is posts.filter(p => p.title.includes(searchQuery)) โ computed on every render, not stored as third piece of state. Storing derived data in state leads to synchronisation bugs.What Belongs in State
| Example | State or Not? | Reason |
|---|---|---|
| List of posts fetched from the API | โ State | Changes after async fetch completes |
| Whether a modal is open | โ State | Changes in response to user click |
| Current form field value | โ State | Changes as user types |
| Loading and error flags | โ State | Change during async operations |
| Whether the user is logged in | โ State (or Context) | Changes on login/logout |
| A post’s title (passed from parent) | โ Prop | Comes from parent, component does not control it |
| Filtered list (derived from posts + filter) | โ Derived | Computed from state โ no need to store separately |
| Post count (derived from posts.length) | โ Derived | Always in sync with posts state โ just use posts.length |
What Happens When State Changes
Initial render:
React calls the component function
useState(0) returns [0, setCount]
Component renders with count = 0
User clicks "Like" โ setLiked(true) is called:
1. React schedules a re-render (does NOT update immediately)
2. React re-calls the component function
3. useState(false) now returns [true, setLiked]
(React remembers the new value for this state slot)
4. Component renders with liked = true
5. React diffs the new output against the previous virtual DOM
6. React updates only the changed DOM nodes (the button text/class)
The component function runs again on every state change โ
that is how React knows what the updated UI looks like.
State Is Preserved Across Re-renders
// React preserves state for a component instance as long as it stays in the tree
// Each time the component re-renders, useState() returns the CURRENT value,
// not the initial value
function LikeButton({ postId }) {
const [liked, setLiked] = useState(false); // initial: false
const [likeCount, setLikeCount] = useState(0); // initial: 0
const handleLike = () => {
setLiked(prev => !prev); // toggle
setLikeCount(prev => prev + (liked ? -1 : 1)); // +1 or -1
};
// On re-render: liked and likeCount hold their current values,
// not the initial false and 0
return (
<button
onClick={handleLike}
className={liked ? 'btn btn--liked' : 'btn'}
>
{liked ? 'โค๏ธ' : '๐ค'} {likeCount}
</button>
);
}
State Is Local โ Each Instance Has Its Own
// Each PostCard instance has its own independent liked state
function PostList({ posts }) {
return (
<div>
{posts.map(post => (
// Each PostCard has its own liked state โ liking one does not affect others
<PostCard key={post._id} post={post} />
))}
</div>
);
}
// If PostCard has useState(false) for liked:
// Post 1: liked = false (independent)
// Post 2: liked = true (user liked this one)
// Post 3: liked = false (independent)
// They do not share state โ each instance is a separate component
Common Mistakes
Mistake 1 โ Storing derived data in state
โ Wrong โ redundant state that can get out of sync:
const [posts, setPosts] = useState([]);
const [filteredPosts, setFilteredPosts] = useState([]); // redundant!
const [searchQuery, setSearchQuery] = useState('');
// Now you have to remember to call setFilteredPosts every time posts or searchQuery changes
// Bugs happen when you forget
โ Correct โ compute filtered list at render time:
const [posts, setPosts] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
// Derived โ computed fresh on every render, always in sync
const filteredPosts = posts.filter(p =>
p.title.toLowerCase().includes(searchQuery.toLowerCase())
);
Mistake 2 โ Confusing state with regular variables
โ Wrong โ changing a regular variable does not cause a re-render:
function Counter() {
let count = 0; // regular variable โ NOT state
return (
<div>
<span>{count}</span>
<button onClick={() => count++}>+</button>
{/* count++ changes the variable but React never re-renders โ UI stays at 0 */}
</div>
);
}
โ Correct โ use useState to track values that should trigger re-renders:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
Mistake 3 โ Putting everything in state
โ Wrong โ tracking values that never change or are only used once:
const [siteTitle, setSiteTitle] = useState('MERN Blog'); // never changes!
const [postCount, setPostCount] = useState(posts.length); // derives from posts
โ Correct โ constants are constants, derived values are computed:
const SITE_TITLE = 'MERN Blog'; // constant โ not state
const postCount = posts.length; // derived from state โ not state itself
Quick Reference
| Concept | Key Point |
|---|---|
| State is owned by | The component that declares it with useState |
| State is changed by | Calling the setter function returned by useState |
| State change causes | The component function to re-run (re-render) |
| State is preserved | As long as the component stays in the React tree |
| State is local | Each component instance has its own state |
| Derived data | Compute from state at render time โ do not store in state |