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

  1. What are Props?
  2. Can You Update Props in Children?
  3. What is State?
  4. Key Differences
  5. When to Use Props
  6. When to Use State
  7. Why Props Over State?
  8. Why State Over Props?
  9. Common Patterns and Best Practices
  10. 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-basics.jsx
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:

How to "Update" Props (The Correct Way):

Since you can't modify props directly, you need to use the "Props Down, Events Up" pattern:

  1. Parent owns the state - The data lives in the parent component's state
  2. Pass data down as props - Parent passes the data to child as props
  3. Pass callback functions as props - Parent passes a function that can update its state
  4. Child calls the callback - When child needs to "update" the prop, it calls the parent's callback
  5. Parent updates its state - Parent's state changes, which updates the prop passed to child
updating-props.jsx
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-basics.jsx
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

AspectPropsState
MutabilityImmutable (read-only)Mutable (can be changed)
OwnershipOwned by parent componentOwned by the component itself
Data FlowTop-down (parent to child)Local to component
InitializationPassed from parentInitialized in component
UpdatesChanged by parent componentChanged by component itself
Use CaseConfiguration, passing data downComponent-specific data that changes
props-vs-state.jsx
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:

when-to-use.jsx
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 logic

6. When to Use State

Use State when you need to:

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:

lifting-state.jsx
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 data

8. Why State Over Props?

Choose State over Props when:

controlled-components.jsx
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:

state-prop-patterns.jsx
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.

common-questions.jsx
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.