What is State in React? State vs Props

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
Note: A common question is “should this be state or a prop?” The answer: if the data changes over the component’s lifetime in response to user actions or async operations, and the component itself controls when it changes โ€” it is state. If the data comes from outside and the component has no control over when it changes โ€” it is a prop. Data that can be computed from existing state or props should be neither โ€” derive it with a simple calculation rather than storing a redundant copy.
Tip: When designing a component, start by identifying the minimum set of state it needs. Every piece of derived data that can be computed from existing state at render time should NOT be stored in state. For example: if you have 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.
Warning: State is local and private to the component that declares it. A component cannot directly read another component’s state. If two components need to share the same data, the state must be “lifted up” to their nearest common ancestor and passed down as props. This is a fundamental React pattern covered in Lesson 4 of this chapter.

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

🧠 Test Yourself

You have posts in state and searchQuery in state. You also store filteredPosts in a third state variable and update it every time either changes. A senior developer reviews your code and says this is wrong. Why?