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.
// 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.
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:
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} />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.
// 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>
)
}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
useRefinstead 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:
// 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