State vs Props in React 📊
Understanding the difference between State and Props is fundamental to React development. This guide will help you understand when to use each, why choose one over the other, and common patterns for managing data flow in your React applications.
Table of content
- What are Props?
- Can You Update Props in Children?
- What is State?
- Key Differences
- When to Use Props
- When to Use State
- Why Props Over State?
- Why State Over Props?
- Common Patterns and Best Practices
- Common Questions & Answers
1. What are Props?
Props (short for properties) are read-only data passed from a parent component to a child component. They allow you to pass data and functions down the component tree, making components reusable and configurable.
Key Characteristics:
- Props are immutable - you cannot modify them within the receiving component
- Props flow downward - from parent to child (unidirectional data flow)
- Props are read-only - attempting to modify props will cause errors
- Props make components reusable and configurable
1// Props are read-only data passed from parent to child
2function WelcomeMessage({ name, age, isActive }) {
3 return (
4 <div>
5 <h1>Welcome, {name}!</h1>
6 <p>Age: {age}</p>
7 <p>Status: {isActive ? 'Active' : 'Inactive'}</p>
8 </div>
9 );
10}
11
12// Parent component passes props
13function App() {
14 return (
15 <WelcomeMessage
16 name="John"
17 age={25}
18 isActive={true}
19 />
20 );
21}
22
23// Props are immutable - this will cause an error
24function BadExample({ count }) {
25 count = count + 1; // ❌ Error: Cannot assign to read-only property
26 return <div>{count}</div>;
27}2. Can You Update Props in Children?
Short Answer: NO - You cannot directly update props in a child component. Props are immutable and read-only.
Why Props Are Immutable:
- Props maintain unidirectional data flow - data flows down from parent to child
- Immutability ensures predictable behavior and prevents bugs
- It enforces the "single source of truth" principle - data is owned by the parent
- Attempting to modify props will result in a runtime error in React
How to "Update" Props (The Correct Way):
Since you can't modify props directly, you need to use the "Props Down, Events Up" pattern:
- Parent owns the state - The data lives in the parent component's state
- Pass data down as props - Parent passes the data to child as props
- Pass callback functions as props - Parent passes a function that can update its state
- Child calls the callback - When child needs to "update" the prop, it calls the parent's callback
- Parent updates its state - Parent's state changes, which updates the prop passed to child
1// ❌ CANNOT UPDATE PROPS DIRECTLY - This will cause an error
2function ChildComponent({ count }) {
3 // Trying to modify props directly - THIS WON'T WORK!
4 count = count + 1; // ❌ Error: Cannot assign to read-only property
5 props.count = 10; // ❌ Error: Cannot assign to read-only property
6
7 return <div>Count: {count}</div>;
8}
9
10// ✅ CORRECT WAY: Use callback functions (Props Down, Events Up)
11function Parent() {
12 const [count, setCount] = useState(0);
13
14 return (
15 <div>
16 <p>Parent count: {count}</p>
17 <ChildComponent
18 count={count} // Pass data down as prop
19 onIncrement={() => setCount(count + 1)} // Pass callback as prop
20 />
21 </div>
22 );
23}
24
25function ChildComponent({ count, onIncrement }) {
26 // Child can't modify props, but can call parent's callback
27 return (
28 <div>
29 <p>Child received count: {count}</p>
30 <button onClick={onIncrement}>
31 Increment (calls parent's function)
32 </button>
33 </div>
34 );
35}
36
37// ✅ ALTERNATIVE: Use local state if child needs independent value
38function ChildWithLocalState({ initialCount }) {
39 // Initialize local state from prop, but manage independently
40 const [count, setCount] = useState(initialCount);
41
42 return (
43 <div>
44 <p>Local count: {count}</p>
45 <button onClick={() => setCount(count + 1)}>
46 Increment (local state)
47 </button>
48 </div>
49 );
50}
51
52// Key Points:
53// 1. Props are IMMUTABLE - you cannot change them
54// 2. To "update" props, parent must update its state
55// 3. Child communicates with parent via callback functions
56// 4. This maintains unidirectional data flow💡 Remember: If a child component needs to modify data, that data should be in the parent's state, not passed as a prop. The child can request changes through callback functions, but the parent always controls the data.
3. What is State?
State is mutable data that belongs to a component. It represents data that can change over time, typically in response to user interactions, network requests, or other events. When state changes, React re-renders the component.
Key Characteristics:
- State is mutable - you can update it using setState or useState
- State is local to the component - each component instance has its own state
- State changes trigger re-renders - React updates the UI when state changes
- State is private - not accessible from other components unless passed as props
1import { useState } from 'react';
2
3// State is mutable data owned by the component
4function Counter() {
5 const [count, setCount] = useState(0);
6 const [name, setName] = useState('Guest');
7
8 return (
9 <div>
10 <h1>Hello, {name}!</h1>
11 <p>Count: {count}</p>
12 <button onClick={() => setCount(count + 1)}>
13 Increment
14 </button>
15 <button onClick={() => setName('John')}>
16 Change Name
17 </button>
18 </div>
19 );
20}
21
22// State changes trigger re-renders
23function Toggle() {
24 const [isOn, setIsOn] = useState(false);
25
26 return (
27 <button onClick={() => setIsOn(!isOn)}>
28 {isOn ? 'ON' : 'OFF'}
29 </button>
30 );
31}4. Key Differences
| Aspect | Props | State |
|---|---|---|
| Mutability | Immutable (read-only) | Mutable (can be changed) |
| Ownership | Owned by parent component | Owned by the component itself |
| Data Flow | Top-down (parent to child) | Local to component |
| Initialization | Passed from parent | Initialized in component |
| Updates | Changed by parent component | Changed by component itself |
| Use Case | Configuration, passing data down | Component-specific data that changes |
1// ❌ WRONG: Trying to modify props
2function UserCard({ name, age }) {
3 // This won't work - props are read-only
4 name = name.toUpperCase(); // Error!
5 return <div>{name}, {age}</div>;
6}
7
8// ✅ CORRECT: Use state for mutable data
9function UserCard({ initialName, age }) {
10 const [name, setName] = useState(initialName);
11
12 const handleNameChange = () => {
13 setName(name.toUpperCase()); // Works!
14 };
15
16 return (
17 <div>
18 <p>{name}, {age}</p>
19 <button onClick={handleNameChange}>Uppercase Name</button>
20 </div>
21 );
22}
23
24// ✅ CORRECT: Use props for passing data down
25function Parent() {
26 const [userName, setUserName] = useState('John');
27
28 return (
29 <ChildComponent
30 name={userName} // Pass as prop
31 onNameChange={setUserName} // Pass setter as prop
32 />
33 );
34}
35
36function ChildComponent({ name, onNameChange }) {
37 return (
38 <div>
39 <p>Name: {name}</p>
40 <button onClick={() => onNameChange('Jane')}>
41 Change Name
42 </button>
43 </div>
44 );
45}5. When to Use Props
Use Props when you need to:
- Pass data from parent to child: When a parent component needs to share data with its children
- Make components reusable: When you want to create generic components that can be configured differently
- Pass callback functions: When child components need to communicate back to parent components
- Configure component behavior: When you want to customize how a component renders or behaves
- Share data across multiple components: When multiple components need access to the same data (lift state up)
1// ✅ USE PROPS WHEN:
2// 1. Passing data from parent to child
3function UserProfile({ user }) {
4 return <div>{user.name} - {user.email}</div>;
5}
6
7// 2. Passing callback functions
8function Button({ onClick, label }) {
9 return <button onClick={onClick}>{label}</button>;
10}
11
12// 3. Configuring child components
13function Modal({ isOpen, title, children }) {
14 if (!isOpen) return null;
15 return (
16 <div>
17 <h2>{title}</h2>
18 {children}
19 </div>
20 );
21}
22
23// ✅ USE STATE WHEN:
24// 1. Data changes over time within component
25function Counter() {
26 const [count, setCount] = useState(0);
27 return <button onClick={() => setCount(count + 1)}>{count}</button>;
28}
29
30// 2. User interactions (form inputs, toggles)
31function LoginForm() {
32 const [email, setEmail] = useState('');
33 const [password, setPassword] = useState('');
34 // ... form logic
35}
36
37// 3. Component-specific UI state
38function Dropdown() {
39 const [isOpen, setIsOpen] = useState(false);
40 return (
41 <div>
42 <button onClick={() => setIsOpen(!isOpen)}>Menu</button>
43 {isOpen && <div>Dropdown content</div>}
44 </div>
45 );
46}
47
48// ❌ DON'T use state for:
49// - Props received from parent
50// - Computed values (use useMemo instead)
51// - Data that doesn't change
52
53// ❌ DON'T use props for:
54// - Data that needs to change within component
55// - Internal component logic6. When to Use State
Use State when you need to:
- Store data that changes: When data needs to be updated within the component (user input, toggles, counters)
- Handle user interactions: When you need to track user actions like form inputs, button clicks, or selections
- Manage UI state: When you need to control UI elements like modals, dropdowns, or loading states
- Store component-specific data: When data is only relevant to that specific component instance
- Handle asynchronous operations: When you need to store data from API calls or other async operations
Remember: If data doesn't change or is only used for rendering, you probably don't need state. Use props or compute it directly.
7. Why Props Over State?
Choose Props over State when:
- Single Source of Truth: When multiple components need the same data, keeping it in a parent component and passing it as props ensures consistency. If you use state in each component, they can get out of sync.
- Data Flow Clarity: Props create a clear, predictable data flow from parent to child, making your code easier to understand and debug.
- Reusability: Components that receive data via props are more reusable because they don't depend on internal state that might not be relevant in different contexts.
- Testability: Components that rely on props are easier to test because you can pass different props and verify the output without worrying about internal state.
- Performance: When data is managed at a higher level and passed down, you have better control over when components re-render, leading to better performance optimization opportunities.
1// Problem: Two siblings need to share state
2// Solution: Lift state up to common parent
3
4function App() {
5 // State lifted to parent component
6 const [count, setCount] = useState(0);
7
8 return (
9 <div>
10 <Display count={count} />
11 <Controls
12 count={count}
13 onIncrement={() => setCount(count + 1)}
14 onDecrement={() => setCount(count - 1)}
15 />
16 </div>
17 );
18}
19
20// Child component receives count as prop
21function Display({ count }) {
22 return <h1>Count: {count}</h1>;
23}
24
25// Another child receives count and setters as props
26function Controls({ count, onIncrement, onDecrement }) {
27 return (
28 <div>
29 <button onClick={onDecrement}>-</button>
30 <span>{count}</span>
31 <button onClick={onIncrement}>+</button>
32 </div>
33 );
34}
35
36// Why lift state up?
37// - Multiple components need the same data
38// - Data needs to stay in sync across components
39// - Single source of truth for shared data8. Why State Over Props?
Choose State over Props when:
- Component-Specific Data: When data is only relevant to a single component and doesn't need to be shared with siblings or parents. Using state keeps the data encapsulated and the component independent.
- Frequent Updates: When data changes frequently and only affects one component, using local state avoids unnecessary re-renders of parent components.
- User Input: For form inputs, toggles, and other interactive elements, local state provides immediate feedback and better user experience without prop drilling.
- Independence: When you want a component to be self-contained and not depend on external data, state makes it more independent and reusable.
- Performance: Local state can be more performant for component-specific UI state (like dropdown open/closed) because it doesn't trigger re-renders in parent components.
1// Controlled Component: Parent controls the value via props
2function App() {
3 const [inputValue, setInputValue] = useState('');
4
5 return (
6 <ControlledInput
7 value={inputValue} // Value comes from parent state
8 onChange={setInputValue} // Parent controls updates
9 />
10 );
11}
12
13function ControlledInput({ value, onChange }) {
14 return (
15 <input
16 value={value} // Controlled by parent
17 onChange={(e) => onChange(e.target.value)}
18 />
19 );
20}
21
22// Uncontrolled Component: Component manages its own state
23function UncontrolledInput() {
24 const [value, setValue] = useState('');
25
26 return (
27 <input
28 value={value} // Controlled internally
29 onChange={(e) => setValue(e.target.value)}
30 />
31 );
32}
33
34// When to use controlled?
35// - Need to access value from parent
36// - Multiple components need to sync
37// - Form validation at parent level
38// - Reset form from parent
39
40// When to use uncontrolled?
41// - Simple local input
42// - No need to access value externally
43// - Better performance (fewer re-renders)9. Common Patterns and Best Practices
Here are some common patterns for using State and Props together:
1// Pattern 1: Props Down, Events Up
2function TodoApp() {
3 const [todos, setTodos] = useState([]);
4
5 const addTodo = (text) => {
6 setTodos([...todos, { id: Date.now(), text }]);
7 };
8
9 return (
10 <div>
11 <TodoInput onAdd={addTodo} /> {/* Pass handler as prop */}
12 <TodoList todos={todos} /> {/* Pass data as prop */}
13 </div>
14 );
15}
16
17function TodoInput({ onAdd }) {
18 const [input, setInput] = useState('');
19
20 const handleSubmit = (e) => {
21 e.preventDefault();
22 onAdd(input); // Call parent's handler
23 setInput('');
24 };
25
26 return (
27 <form onSubmit={handleSubmit}>
28 <input
29 value={input}
30 onChange={(e) => setInput(e.target.value)}
31 />
32 </form>
33 );
34}
35
36function TodoList({ todos }) {
37 return (
38 <ul>
39 {todos.map(todo => (
40 <li key={todo.id}>{todo.text}</li>
41 ))}
42 </ul>
43 );
44}
45
46// Pattern 2: Derived State from Props
47function UserGreeting({ user }) {
48 // Don't copy props to state!
49 // const [name, setName] = useState(user.name); // ❌ BAD
50
51 // ✅ GOOD: Use props directly
52 return <h1>Hello, {user.name}!</h1>;
53
54 // ✅ GOOD: If you need to modify, use derived state
55 const displayName = user.name.toUpperCase();
56 return <h1>Hello, {displayName}!</h1>;
57}
58
59// Pattern 3: State with Props Initialization
60function Counter({ initialCount = 0 }) {
61 // ✅ GOOD: Initialize state from prop, but manage it internally
62 const [count, setCount] = useState(initialCount);
63
64 // State is independent after initialization
65 return (
66 <button onClick={() => setCount(count + 1)}>
67 {count}
68 </button>
69 );
70}Best Practices Summary
- Props Down, Events Up: Pass data down via props, send events up via callback functions
- Lift State Up: When multiple components need the same data, lift it to their common parent
- Don't Copy Props to State: Use props directly unless you need to modify them (then use state)
- Single Source of Truth: Keep state in one place - either in the component that needs it or in a common parent
- Minimize State: Only use state for data that actually changes. Use props for static or computed data
- Controlled vs Uncontrolled: Use controlled components (props) when you need to control the value from parent, use uncontrolled (state) for simple local inputs
Quick Decision Guide
Use Props if:
- Data comes from parent component
- Multiple components need the same data
- Data is used for configuration
- You want to make component reusable
Use State if:
- Data changes within the component
- Data is only relevant to this component
- Handling user input or interactions
- Managing UI state (modals, dropdowns, etc.)
10. Common Questions & Answers
Here are answers to frequently asked questions about State and Props in React. These questions often come up during development and interviews.
Q1: Can you pass state as props?
Yes! This is the most common pattern in React. You can pass any value as a prop, including state values.
Q2: Should you copy props to state?
Generally no. Use props directly unless you need to modify them independently. Copying props to state can lead to bugs when props change but your local state doesn't update.
Exception: It's okay to initialize state from props if the state needs to be independent after initialization (like a counter that starts from a prop value).
Q3: Can props be functions?
Yes! Functions are first-class values in JavaScript. You can pass functions as props, which is how the "Props Down, Events Up" pattern works.
Q4: What happens if you don't pass a prop?
The prop will be undefined. Always use default parameters or optional chaining to handle missing props gracefully.
Q5: Can you have both props and state in the same component?
Absolutely! This is a very common pattern. Use props for data from parent, and state for component-specific data that changes.
Q6: How do you share state between sibling components?
Lift state up to their common parent. The parent manages the state and passes it down as props to both siblings. If one sibling needs to update it, pass a callback function as a prop.
Q7: Can props be objects or arrays?
Yes! Any JavaScript value can be a prop - strings, numbers, booleans, objects, arrays, functions, even JSX elements.
Q8: What's the difference between controlled and uncontrolled components?
Controlled: Parent controls the value via props. The component receives value and onChange as props.
Uncontrolled: Component manages its own state internally. The parent doesn't control the value.
Q9: Can you use props to initialize state?
Yes, but remember: state becomes independent after initialization. If the prop changes later, your state won't automatically update. Use useEffect if you need to sync state with prop changes.
Q10: How do you sync state with prop changes?
Use useEffect to watch for prop changes and update your state accordingly. This is useful when you initialize state from props but want it to stay in sync.
1// Q1: Can you pass state as props?
2// ✅ YES - This is the most common pattern
3function Parent() {
4 const [count, setCount] = useState(0);
5 return <Child count={count} />; // State passed as prop
6}
7
8// Q2: Should you copy props to state?
9// ❌ GENERALLY NO - Use props directly
10function BadExample({ name }) {
11 const [localName, setLocalName] = useState(name); // ❌ BAD
12 // Problem: If prop changes, local state won't update
13}
14
15// ✅ GOOD: Use props directly
16function GoodExample({ name }) {
17 return <div>{name}</div>; // Use prop directly
18}
19
20// ✅ EXCEPTION: Only copy to state if you need to modify it independently
21function Counter({ initialCount }) {
22 const [count, setCount] = useState(initialCount); // ✅ OK
23 // State is independent after initialization
24}
25
26// Q3: Can props be functions?
27// ✅ YES - Functions are first-class values in JavaScript
28function Parent() {
29 const handleClick = () => console.log('Clicked');
30 return <Button onClick={handleClick} />;
31}
32
33function Button({ onClick }) {
34 return <button onClick={onClick}>Click me</button>;
35}
36
37// Q4: What happens if you don't pass a prop?
38// Props are undefined if not passed
39function Greeting({ name }) {
40 return <div>Hello, {name || 'Guest'}!</div>;
41}
42
43// Better: Use default parameters
44function Greeting({ name = 'Guest' }) {
45 return <div>Hello, {name}!</div>;
46}
47
48// Q5: Can you have both props and state in the same component?
49// ✅ YES - Very common pattern
50function UserCard({ user }) { // Props
51 const [isExpanded, setIsExpanded] = useState(false); // State
52
53 return (
54 <div>
55 <h3>{user.name}</h3>
56 {isExpanded && <p>{user.bio}</p>}
57 <button onClick={() => setIsExpanded(!isExpanded)}>
58 Toggle
59 </button>
60 </div>
61 );
62}
63
64// Q6: How do you share state between sibling components?
65// Solution: Lift state up to common parent
66function App() {
67 const [count, setCount] = useState(0); // Shared state
68
69 return (
70 <div>
71 <Display count={count} /> {/* Sibling 1 */}
72 <Controls
73 count={count}
74 setCount={setCount}
75 /> {/* Sibling 2 */}
76 </div>
77 );
78}
79
80// Q7: Can props be objects or arrays?
81// ✅ YES - Any JavaScript value can be a prop
82function UserList({ users }) { // Array prop
83 return (
84 <ul>
85 {users.map(user => (
86 <li key={user.id}>{user.name}</li>
87 ))}
88 </ul>
89 );
90}
91
92function UserProfile({ user }) { // Object prop
93 return <div>{user.name} - {user.email}</div>;
94}
95
96// Q8: What's the difference between controlled and uncontrolled?
97// Controlled: Parent controls value via props
98function ControlledInput({ value, onChange }) {
99 return (
100 <input
101 value={value} // Controlled by parent
102 onChange={(e) => onChange(e.target.value)}
103 />
104 );
105}
106
107// Uncontrolled: Component manages its own state
108function UncontrolledInput() {
109 const [value, setValue] = useState('');
110 return (
111 <input
112 value={value} // Controlled internally
113 onChange={(e) => setValue(e.target.value)}
114 />
115 );
116}
117
118// Q9: Can you use props to initialize state?
119// ✅ YES - But state becomes independent after initialization
120function Counter({ initialCount = 0 }) {
121 const [count, setCount] = useState(initialCount);
122 // If initialCount prop changes later, count won't update
123 // Use useEffect if you need to sync with prop changes
124 return <button onClick={() => setCount(count + 1)}>{count}</button>;
125}
126
127// Q10: How to sync state with prop changes?
128// Use useEffect to sync state when prop changes
129function SyncedCounter({ count: propCount }) {
130 const [count, setCount] = useState(propCount);
131
132 useEffect(() => {
133 setCount(propCount); // Sync when prop changes
134 }, [propCount]);
135
136 return <div>{count}</div>;
137}💡 Pro Tip
When in doubt, ask yourself: "Who owns this data?" If the parent component owns it, use props. If the component itself owns it, use state. If multiple components need it, lift the state up to their common parent and pass it down as props.