Components & Props
Components are the building blocks of any React application. They let you split the UI into independent, reusable pieces, and think about each piece in isolation.
Component Mental Model
Think of components like LEGO blocks:
┌─────────────────────────────────────────────────────────────────┐
│ App Component │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Header Component │ │
│ │ ┌──────────────┐ ┌──────────────────────────────────┐ │ │
│ │ │ Logo │ │ Navigation │ │ │
│ │ └──────────────┘ └──────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Main Content │ │
│ │ ┌─────────────────┐ ┌─────────────────────────────┐ │ │
│ │ │ ProductCard │ │ ProductCard │ │ │
│ │ └─────────────────┘ └─────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Footer Component │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Functional Components
In modern React, we primarily use Functional Components . They are simply JavaScript functions that return JSX.
// Function Declaration
function Welcome () {
return < h1 > Hello, World </ h1 > ;
}
// Arrow Function (more common)
const Welcome = () => {
return < h1 > Hello, World </ h1 > ;
};
// Arrow Function with implicit return
const Welcome = () => < h1 > Hello, World </ h1 > ;
Component Naming Rules
Component names must start with a capital letter . React treats lowercase elements as HTML tags. // ❌ Wrong - React treats this as an HTML tag
const welcome = () => < h1 > Hi </ h1 > ;
< welcome /> // Renders as <welcome></welcome>
// ✅ Correct
const Welcome = () => < h1 > Hi </ h1 > ;
< Welcome /> // Renders the component
Importing and Exporting Components
// Welcome.jsx - Named export
export const Welcome = () => < h1 > Hello </ h1 > ;
// Welcome.jsx - Default export
const Welcome = () => < h1 > Hello </ h1 > ;
export default Welcome ;
// App.jsx - Importing
import Welcome from './Welcome' ; // Default import
import { Welcome } from './Welcome' ; // Named import
import { Welcome as Greeting } from './Welcome' ; // Renamed import
Props (Properties)
Props are the way to pass data from parent to child components. Think of them as function arguments.
Passing Props
function App () {
return (
< div >
< Welcome name = "Sara" age = { 25 } isOnline = { true } />
< Welcome name = "Bob" age = { 30 } isOnline = { false } />
</ div >
);
}
Receiving Props
The component receives all props as a single object.
// Method 1: Props object
function Welcome ( props ) {
return (
< h1 >
Hello, { props . name } . You are { props . age } years old.
{ props . isOnline && < span > 🟢 </ span > }
</ h1 >
);
}
// Method 2: Destructuring (recommended)
function Welcome ({ name , age , isOnline }) {
return (
< h1 >
Hello, { name } . You are { age } years old.
{ isOnline && < span > 🟢 </ span > }
</ h1 >
);
}
Props Are Read-Only (Immutable)
Never modify props! Props are read-only. Attempting to change them violates React’s core principles.// ❌ NEVER do this
function BadComponent ( props ) {
props . name = 'Changed' ; // ERROR!
return < h1 > { props . name } </ h1 > ;
}
// ✅ If you need to modify data, use state (covered in next chapter)
Default Props
You can provide default values for props when they’re not passed.
// Method 1: Default parameters (recommended)
function Button ({ color = 'blue' , size = 'medium' , text }) {
return (
< button
style = { { backgroundColor: color } }
className = { `btn- ${ size } ` }
>
{ text }
</ button >
);
}
// Method 2: defaultProps (older approach)
function Button ({ color , size , text }) {
// ...
}
Button . defaultProps = {
color: 'blue' ,
size: 'medium'
};
Children Prop
children is a special prop that allows you to pass components or content between the opening and closing tags.
function Card ({ children , title }) {
return (
< div className = "card" >
< h2 className = "card-title" > { title } </ h2 >
< div className = "card-body" >
{ children }
</ div >
</ div >
);
}
function App () {
return (
< Card title = "Welcome" >
< p > This is the card content. </ p >
< button > Click me </ button >
</ Card >
);
}
Practical Children Patterns
// Layout component
function PageLayout ({ children }) {
return (
< div className = "page" >
< Header />
< main > { children } </ main >
< Footer />
</ div >
);
}
// Modal component
function Modal ({ children , isOpen , onClose }) {
if ( ! isOpen ) return null ;
return (
< div className = "modal-overlay" onClick = { onClose } >
< div className = "modal-content" onClick = { e => e . stopPropagation () } >
{ children }
</ div >
</ div >
);
}
// Usage
< Modal isOpen = { showModal } onClose = { () => setShowModal ( false ) } >
< h2 > Confirm Action </ h2 >
< p > Are you sure you want to proceed? </ p >
< button onClick = { handleConfirm } > Yes </ button >
</ Modal >
Prop Types (Runtime Validation)
PropTypes provide runtime type checking for your components.
import PropTypes from 'prop-types' ;
function UserCard ({ name , age , email , isAdmin , avatar }) {
return (
< div className = "user-card" >
< img src = { avatar } alt = { name } />
< h2 > { name } </ h2 >
< p > Age: { age } </ p >
< p > Email: { email } </ p >
{ isAdmin && < span className = "badge" > Admin </ span > }
</ div >
);
}
UserCard . propTypes = {
name: PropTypes . string . isRequired ,
age: PropTypes . number . isRequired ,
email: PropTypes . string . isRequired ,
isAdmin: PropTypes . bool ,
avatar: PropTypes . string
};
UserCard . defaultProps = {
isAdmin: false ,
avatar: '/default-avatar.png'
};
Common PropTypes
PropType Description PropTypes.stringString value PropTypes.numberNumber value PropTypes.boolBoolean value PropTypes.arrayArray PropTypes.objectObject PropTypes.funcFunction PropTypes.nodeAnything renderable (string, number, element) PropTypes.elementReact element PropTypes.oneOf(['a', 'b'])One of specific values PropTypes.arrayOf(PropTypes.number)Array of specific type PropTypes.shape({ name: PropTypes.string })Object with specific shape
For production apps, consider using TypeScript instead of PropTypes. It provides compile-time checking and better IDE support.
Component Composition Patterns
1. Container and Presentational Components
Separate logic from presentation:
// Presentational Component (dumb) - only handles display
function UserList ({ users , onSelect }) {
return (
< ul >
{ users . map ( user => (
< li key = { user . id } onClick = { () => onSelect ( user ) } >
{ user . name }
</ li >
)) }
</ ul >
);
}
// Container Component (smart) - handles logic
function UserListContainer () {
const [ users , setUsers ] = useState ([]);
const [ selectedUser , setSelectedUser ] = useState ( null );
useEffect (() => {
fetch ( '/api/users' )
. then ( res => res . json ())
. then ( setUsers );
}, []);
return < UserList users = { users } onSelect = { setSelectedUser } /> ;
}
2. Compound Components
Components that work together to form a complete UI:
// Accordion compound component
function Accordion ({ children }) {
const [ openIndex , setOpenIndex ] = useState ( null );
return (
< div className = "accordion" >
{ React . Children . map ( children , ( child , index ) =>
React . cloneElement ( child , {
isOpen: index === openIndex ,
onToggle : () => setOpenIndex ( index === openIndex ? null : index )
})
) }
</ div >
);
}
function AccordionItem ({ title , children , isOpen , onToggle }) {
return (
< div className = "accordion-item" >
< button onClick = { onToggle } className = "accordion-header" >
{ title }
< span > { isOpen ? '−' : '+' } </ span >
</ button >
{ isOpen && < div className = "accordion-content" > { children } </ div > }
</ div >
);
}
// Usage
< Accordion >
< AccordionItem title = "Section 1" > Content 1 </ AccordionItem >
< AccordionItem title = "Section 2" > Content 2 </ AccordionItem >
< AccordionItem title = "Section 3" > Content 3 </ AccordionItem >
</ Accordion >
3. Render Props
Pass a function as a child to share logic:
function MouseTracker ({ children }) {
const [ position , setPosition ] = useState ({ x: 0 , y: 0 });
useEffect (() => {
const handleMove = ( e ) => setPosition ({ x: e . clientX , y: e . clientY });
window . addEventListener ( 'mousemove' , handleMove );
return () => window . removeEventListener ( 'mousemove' , handleMove );
}, []);
return children ( position );
}
// Usage
< MouseTracker >
{ ({ x , y }) => (
< div > Mouse position: { x } , { y } </ div >
) }
</ MouseTracker >
Spreading Props
You can spread all props to a child component:
function Button ({ className , children , ... rest }) {
return (
< button className = { `btn ${ className } ` } { ... rest } >
{ children }
</ button >
);
}
// All native button props (onClick, disabled, type, etc.) are passed through
< Button
className = "primary"
onClick = { handleClick }
disabled = { isLoading }
type = "submit"
>
Submit
</ Button >
🎯 Practice Exercises
Exercise 1: Create a Reusable Alert Component
Build an Alert component that accepts:
type: ‘success’ | ‘warning’ | ‘error’
title: string
children: content
function Alert ({ type = 'info' , title , children }) {
const styles = {
success: { bg: '#d1fae5' , border: '#10b981' , color: '#065f46' },
warning: { bg: '#fef3c7' , border: '#f59e0b' , color: '#92400e' },
error: { bg: '#fee2e2' , border: '#ef4444' , color: '#991b1b' },
info: { bg: '#dbeafe' , border: '#3b82f6' , color: '#1e40af' }
};
const style = styles [ type ];
return (
< div style = { {
backgroundColor: style . bg ,
borderLeft: `4px solid ${ style . border } ` ,
padding: '16px' ,
marginBottom: '16px'
} } >
{ title && < strong style = { { color: style . color } } > { title } </ strong > }
< p style = { { color: style . color , margin: title ? '8px 0 0' : 0 } } >
{ children }
</ p >
</ div >
);
}
// Usage
< Alert type = "success" title = "Success!" >
Your changes have been saved.
</ Alert >
< Alert type = "error" >
Something went wrong. Please try again.
</ Alert >
Exercise 2: Build a Card System
Create a flexible card system with Card, Card.Header, Card.Body, and Card.Footer: function Card ({ children , className = '' }) {
return (
< div className = { `card ${ className } ` } style = { {
border: '1px solid #e5e7eb' ,
borderRadius: '8px' ,
overflow: 'hidden'
} } >
{ children }
</ div >
);
}
Card . Header = function CardHeader ({ children }) {
return (
< div style = { { padding: '16px' , borderBottom: '1px solid #e5e7eb' } } >
{ children }
</ div >
);
};
Card . Body = function CardBody ({ children }) {
return < div style = { { padding: '16px' } } > { children } </ div > ;
};
Card . Footer = function CardFooter ({ children }) {
return (
< div style = { {
padding: '16px' ,
borderTop: '1px solid #e5e7eb' ,
backgroundColor: '#f9fafb'
} } >
{ children }
</ div >
);
};
// Usage
< Card >
< Card.Header >
< h3 > Card Title </ h3 >
</ Card.Header >
< Card.Body >
< p > Card content goes here. </ p >
</ Card.Body >
< Card.Footer >
< button > Action </ button >
</ Card.Footer >
</ Card >
Exercise 3: Avatar Component with Fallback
Create an Avatar component that shows initials if no image is provided: function Avatar ({ src , name , size = 40 }) {
const getInitials = ( name ) => {
return name
. split ( ' ' )
. map ( word => word [ 0 ])
. join ( '' )
. toUpperCase ()
. slice ( 0 , 2 );
};
const colors = [ '#ef4444' , '#f97316' , '#eab308' , '#22c55e' , '#3b82f6' , '#8b5cf6' ];
const colorIndex = name . charCodeAt ( 0 ) % colors . length ;
const style = {
width: size ,
height: size ,
borderRadius: '50%' ,
display: 'flex' ,
alignItems: 'center' ,
justifyContent: 'center' ,
fontSize: size * 0.4 ,
fontWeight: 'bold' ,
color: 'white' ,
backgroundColor: colors [ colorIndex ]
};
if ( src ) {
return (
< img
src = { src }
alt = { name }
style = { { ... style , objectFit: 'cover' } }
onError = { ( e ) => e . target . style . display = 'none' }
/>
);
}
return < div style = { style } > { getInitials ( name ) } </ div > ;
}
// Usage
< Avatar src = "/user.jpg" name = "John Doe" size = { 50 } />
< Avatar name = "Jane Smith" size = { 40 } />
Summary
Concept Description Functional Components Functions that return JSX Props Arguments passed to components (read-only) Destructuring Extract props for cleaner code Default Props Fallback values when props aren’t provided children Special prop for nested content PropTypes Runtime type checking Composition Building complex UIs from simple components Spreading Props Pass all props with {...props}
Next Steps In the next chapter, you’ll learn about State & useState — how to make your components interactive and dynamic!