ReactState vs Props

State vs Props

Props and state are the two data mechanisms in React, and they are often confused by beginners. The distinction is fundamental: props are data passed into a component from outside, while state is data that a component creates and manages for itself. Understanding when to use each is one of the most important React skills.

The Key Differences

Props

State

Source

Comes from the parent

Created inside the component

Who controls it

The parent component

The component itself

Mutable?

No — read-only in the child

Yes — via the setter function

Triggers re-render?

Yes, when the parent re-renders

Yes, when the setter is called

Visible to parent?

Yes — parent chose the value

No — private unless lifted up

Lifetime

Depends on the parent

Lives as long as the component

Props: External, Parent-Controlled

Props flow down from a parent component. The child component receives them as a snapshot and renders accordingly. The child cannot change its own props — only the parent can, by re-rendering with new values.

JSX
// Greeting only renders what it is told — it has no say in the name
function Greeting({ name, role }) {
  return <h2>Hello, {name}! You are a {role}.</h2>
}

// The parent controls the data:
function App() {
  return <Greeting name="Alice" role="developer" />
}
State: Internal, Component-Controlled

State is data a component owns and manages itself. No other component can read or write it directly. When the component calls its setter function, only that component (and its children) re-renders.

JSX
import { useState } from 'react'

// Counter owns and manages its own count — no parent involvement needed
function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}
Using Both Props and State Together

A component can use both props and state at the same time. Props provide the initial configuration from the parent; state handles the dynamic behaviour over the component's life:

JSX
import { useState } from 'react'

function LikeButton({ label, initialCount }) {
  // Props: label and initialCount come from the parent
  // State: the current like count is managed internally
  const [likes, setLikes] = useState(initialCount)
  const [liked, setLiked] = useState(false)

  function handleLike() {
    if (!liked) {
      setLikes(likes + 1)
      setLiked(true)
    } else {
      setLikes(likes - 1)
      setLiked(false)
    }
  }

  return (
    <button
      onClick={handleLike}
      style={{ color: liked ? 'crimson' : 'gray' }}
    >
      {liked ? '♥' : '♡'} {label} · {likes}
    </button>
  )
}

// Parent passes the initial data; LikeButton manages the rest
<LikeButton label="React" initialCount={142} />
Note
Notice that `initialCount` is used only to seed `useState`. After the first render, changing `initialCount` from the parent does NOT update the internal `likes` state. If you need the component to stay in sync with an external value, that is a clue you need a controlled component pattern instead.
Controlled vs Uncontrolled Components

The props/state distinction maps directly to the controlled vs uncontrolled component pattern. A controlled component keeps all of its data in the parent's state and receives it via props. An uncontrolled component manages its own internal state.

JSX
// UNCONTROLLED — manages its own value internally
function UncontrolledInput() {
  const [value, setValue] = useState('')
  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  )
}

// CONTROLLED — the parent owns the value; the component is a dumb display + event emitter
function ControlledInput({ value, onChange }) {
  return (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
    />
  )
}

function Form() {
  const [email, setEmail] = useState('')

  return (
    <form>
      <ControlledInput value={email} onChange={setEmail} />
      <p>You typed: {email}</p>
    </form>
  )
}
Tip
Controlled components are more predictable — the parent always knows the current value and can validate, transform, or reset it. Uncontrolled components are simpler but the parent cannot inspect or control the value without a ref.
Deciding: Should This Be State or a Prop?

Ask yourself these questions:

  • Does the parent need to read or control this value? → Lift it into the parent as state and pass it down as a prop.

  • Does only this component care about the value? → Keep it as local state.

  • Can the value be computed from existing props or state? → Do not make it state at all — compute it on each render.

  • Does the value need to persist across renders without triggering a re-render? → Use useRef instead of state.

Lifting State Up — a Preview

When two sibling components need to share the same data, the solution is to lift the state up to their closest common ancestor. The ancestor holds the state and passes it down as props to both siblings:

JSX
// Both Display and Controls need access to 'count'
// → lift it into their parent

function App() {
  const [count, setCount] = useState(0)   // state lives here

  return (
    <div>
      <Display count={count} />              {/* receives count as a prop */}
      <Controls onIncrement={() => setCount(count + 1)} />
    </div>
  )
}

function Display({ count }) {
  return <p>Current count: {count}</p>
}

function Controls({ onIncrement }) {
  return <button onClick={onIncrement}>Increment</button>
}
  • Props = external, parent-controlled, read-only in the child

  • State = internal, component-controlled, mutable via the setter

  • Both trigger re-renders when they change

  • A component can use both simultaneously

  • Controlled components use props for their value; uncontrolled components use local state

  • When siblings share data, lift the state up to their parent