What are Props?
Props (short for "properties") are how React components receive data from their parent components. Think of props as function parameters - they allow you to pass information into a component to customize its behavior and appearance.
// Function with parameters
function greetUser(name, age) {
return `Hello ${name}, you are ${age} years old!`;
}
// Calling the function
greetUser("Alice", 25);
greetUser("Bob", 30);
// Component with props
function GreetUser({ name, age }) {
return <p>Hello {name}, you are {age} years old!</p>;
}
// Using the component
<GreetUser name="Alice" age={25} />
<GreetUser name="Bob" age={30} />
🔑 Key Props Concepts
- Props are read-only - Components cannot modify their props
- Props flow downward - From parent to child components
- Props are like function arguments - They configure how a component behaves
- Props can be any JavaScript value - strings, numbers, objects, functions, etc.
Props in Action
Let's see how different types of data can be passed as props:
// UserCard component that accepts multiple props
function UserCard({
name, // string
age, // number
isOnline, // boolean
avatar, // string (URL)
hobbies, // array
address, // object
onFollow // function
}) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name} ({age}){isOnline && ' 🟢'}</h3>
<p>{address.city}, {address.country}</p>
<div>
<strong>Hobbies:</strong>
{hobbies.map(hobby => (
<span key={hobby} className="hobby-tag">{hobby}</span>
))}
</div>
<button onClick={onFollow}>Follow</button>
</div>
);
}
// Using the component
<UserCard
name="Sarah Johnson"
age={28}
isOnline={true}
avatar="/images/sarah.jpg"
hobbies={['Photography', 'Hiking', 'Cooking']}
address={{ city: 'San Francisco', country: 'USA' }}
onFollow={() => console.log('Following Sarah!')}
/>
What is State?
State is data that belongs to a component and can change over time. Unlike props (which come from outside), state is managed internally by the component. When state changes, React automatically re-renders the component.
// Managing state manually
let count = 0;
const counterElement = document.getElementById('counter');
const buttonElement = document.getElementById('button');
function updateDisplay() {
counterElement.textContent = `Count: ${count}`;
}
buttonElement.addEventListener('click', () => {
count++;
updateDisplay(); // Manual update
});
updateDisplay(); // Initial render
import React, { useState } from 'react';
function Counter() {
// State is managed by React
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
// React automatically re-renders when state changes
}
The useState Hook
The useState hook is the most common way to add state to functional components. It returns an array with two elements: the current state value and a function to update it.
import React, { useState } from 'react';
function MyComponent() {
// Syntax: const [stateVariable, setterFunction] = useState(initialValue);
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [isVisible, setIsVisible] = useState(false);
const [users, setUsers] = useState([]);
const [user, setUser] = useState({ name: '', email: '' });
return (
<div>
{/* Using state in JSX */}
<p>Count: {count}</p>
<p>Name: {name}</p>
<p>Visibility: {isVisible ? 'Visible' : 'Hidden'}</p>
{/* Updating state */}
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setIsVisible(!isVisible)}>
Toggle Visibility
</button>
</div>
);
}
📝 useState Rules
- Always use the setter function - Never modify state directly
- State updates are asynchronous - React batches updates for performance
- State updates trigger re-renders - Component will re-render when state changes
- Use functional updates for complex changes - Pass a function to the setter when the new state depends on the previous state
State Update Patterns
🔢 Simple Values
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// Direct updates
setCount(5);
setName('John');
// Functional updates (when depending on previous value)
setCount(prevCount => prevCount + 1);
setName(prevName => prevName.toUpperCase());
📋 Arrays
const [items, setItems] = useState([]);
// Add item
setItems([...items, newItem]);
// Remove item
setItems(items.filter(item => item.id !== targetId));
// Update item
setItems(items.map(item =>
item.id === targetId
? { ...item, name: 'Updated' }
: item
));
🏷️ Objects
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
// Update single property
setUser({ ...user, name: 'John' });
// Update multiple properties
setUser(prevUser => ({
...prevUser,
name: 'John',
age: 25
}));
🔄 Complex State
// Separate concerns with multiple useState
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState([]);
// Or group related state
const [formState, setFormState] = useState({
name: '',
email: '',
errors: {}
});
Props vs State: When to Use What?
- Data comes from parent - Configuration, user info, etc.
- Data doesn't change in this component - Display-only information
- Multiple components need the same data - Shared state managed by parent
- Customizing component behavior - Settings, callbacks, etc.
// UserProfile receives data via props
function UserProfile({ user, onEdit }) {
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={onEdit}>Edit</button>
</div>
);
}
- Data changes over time - User input, API responses
- Component owns the data - Internal component logic
- Triggering re-renders - UI updates based on user interaction
- Temporary data - Form inputs, modal visibility
// EditForm manages its own state
function EditForm({ user, onSave }) {
const [name, setName] = useState(user.name);
const [email, setEmail] = useState(user.email);
return (
<form>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<input
value={email}
onChange={e => setEmail(e.target.value)}
/>
<button onClick={() => onSave({name, email})}>
Save
</button>
</form>
);
}
Data Flow in Action
Let's build a practical example that demonstrates how props and state work together in a real application:
import React, { useState } from 'react';
// Child component - receives props, no state needed
function TodoItem({ todo, onToggle, onDelete }) {
return (
<div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
<span onClick={() => onToggle(todo.id)}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>
Delete
</button>
</div>
);
}
// Child component - manages its own input state
function AddTodo({ onAdd }) {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
onAdd(text);
setText(''); // Reset form
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a todo..."
/>
<button type="submit">Add</button>
</form>
);
}
// Parent component - manages shared state
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', completed: false },
{ id: 2, text: 'Build a project', completed: false }
]);
const addTodo = (text) => {
const newTodo = {
id: Date.now(),
text,
completed: false
};
setTodos([...todos, newTodo]);
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div className="todo-app">
<h1>My Todos</h1>
<AddTodo onAdd={addTodo} />
<div>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</div>
</div>
);
}
🔄 Data Flow Breakdown
- TodoApp manages the main todos state (array of todo objects)
- TodoApp passes individual todos and callback functions as props to TodoItem
- TodoItem receives props and calls callbacks when user interacts
- AddTodo manages its own input state and calls the onAdd callback
- State changes in TodoApp trigger re-renders of all child components
Best Practices
✅ State Management
- Keep state as simple as possible
- Use multiple useState for unrelated data
- Don't duplicate props in state
- Lift state up when multiple components need it
✅ Props Handling
- Use destructuring for cleaner code
- Provide default values for optional props
- Use meaningful prop names
- Consider PropTypes for validation
✅ Performance
- Don't create objects/functions in render
- Use functional updates for performance
- Consider React.memo for expensive components
- Keep components small and focused
❌ Common Mistakes
- Never modify state directly
- Don't use array indices as keys
- Avoid too much state in one component
- Don't forget dependencies in useEffect
What's Next?
You now understand how data flows through React applications using props and state. Next, you'll learn how to make your components respond to user interactions.
🚀 Continue Learning
- Event Handling - Make your components interactive
- Conditional Rendering - Show different content based on state
- React Hooks - Master useEffect and other hooks
- Advanced State Management - Context API and beyond