Chapter 24 โ Props vs State
๐ Definition
- Props โ read-only inputs passed from a parent to a child component.
- State โ internal, mutable data managed within a component.
๐ Explanation
| Aspect | Props | State |
|---|---|---|
| Owner | Parent | The component itself |
| Mutable inside the component? | โ No (immutable) | โ Yes (via setter) |
| Triggers re-render? | When parent passes a new value | When setter is called |
| Purpose | Configuration / data passing | Local UI behavior |
| Initial value? | Always from parent | Set on first render |
๐ป Code Example โ Props
function Welcome({ name, role }) {
return <h2>Welcome, {role} {name}!</h2>;
}
// Parent
<Welcome name="Soumya" role="Engineer" />name and role are received in props and are read-only inside Welcome.
๐ป Code Example โ State
function Counter() {
const [count, setCount] = useState(0); // initial state = 0
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>โ</button>
</div>
);
}๐ป Code Example โ Props + State Together
function Counter({ initial = 0 }) { // props = initial value
const [count, setCount] = useState(initial); // state holds current count
return (
<button onClick={() => setCount(count + 1)}>{count}</button>
);
}
<Counter initial={5} />๐ป Code Example โ Lifting State Up
When two siblings need to share state, move it to their common parent.
function TemperatureInput({ value, onChange }) { // โ controlled by parent
return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}
function Converter() {
const [celsius, setCelsius] = useState("");
return (
<>
<TemperatureInput value={celsius} onChange={setCelsius} />
<p>Fahrenheit: {celsius ? (celsius * 9 / 5 + 32) : "-"}</p>
</>
);
}๐ป Code Example โ State Updates Are Asynchronous
function Counter() {
const [count, setCount] = useState(0);
const tripleIncrement = () => {
setCount(count + 1); // queues 1
setCount(count + 1); // STILL queues 1 โ `count` is stale
setCount(count + 1); // STILL queues 1
};
// โ result: count becomes 1, not 3
const tripleIncrementFixed = () => {
setCount(c => c + 1); // โ
functional updates
setCount(c => c + 1);
setCount(c => c + 1);
};
}Rule: Use the functional form
setX(prev => โฆ)when the new state depends on the previous state.
๐ป Code Example โ Don't Mutate State
// โ wrong โ React doesn't detect the change
const addItem = (item) => {
state.items.push(item);
setItems(state.items); // same reference โ no re-render!
};
// โ
right โ create a new array
const addItem = (item) => {
setItems(prev => [...prev, item]);
};
// โ
for objects
setUser(prev => ({ ...prev, name: "New" }));๐ป Code Example โ useState with Complex State
function Form() {
const [form, setForm] = useState({ name: "", email: "" });
const update = (field) => (e) =>
setForm(prev => ({ ...prev, [field]: e.target.value }));
return (
<>
<input value={form.name} onChange={update("name")} />
<input value={form.email} onChange={update("email")} />
</>
);
}For complex state shapes, consider useReducer:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: "UPDATE_FIELD", field: "name", value: "Soumya" });๐ป Code Example โ Props Drilling Problem
function App() {
const [theme, setTheme] = useState("light");
return <Page theme={theme} />;
}
function Page({ theme }) { return <Sidebar theme={theme} />; }
function Sidebar({ theme }) { return <Menu theme={theme} />; }
function Menu({ theme }) { return <span>{theme}</span>; }
// theme is "drilled" through three components that don't use it.
// Solution: Context API (Chapter 18).๐ฏ Likely Interview Questions
- Difference between props and state.
- Can a component change its own props? โ No; props are immutable inside the receiver.
- What is lifting state up?
- Why use the functional form of
setState? โ When new state depends on the previous, the variable in closure may be stale. - Why immutability for state? โ React uses reference equality (
prev !== next) to decide whether to re-render.