Component Patterns and Best Practices
Now that you understand props and components, let's explore patterns for organizing and structuring React components effectively.
Component File Organization
There are several ways to organize component files:
Single Component per File
1src/2├── components/3│ ├── Button.jsx4│ ├── Card.jsx5│ ├── Header.jsx6│ └── Footer.jsx
Feature-Based Organization
1src/2├── features/3│ ├── auth/4│ │ ├── LoginForm.jsx5│ │ ├── SignupForm.jsx6│ │ └── AuthContext.jsx7│ ├── products/8│ │ ├── ProductCard.jsx9│ │ ├── ProductList.jsx10│ │ └── ProductDetail.jsx
Component with Styles and Tests
1src/2├── components/3│ ├── Button/4│ │ ├── Button.jsx5│ │ ├── Button.module.css6│ │ ├── Button.test.jsx7│ │ └── index.js
Choose based on project size and team preferences. Consistency matters most.
Naming Conventions
Follow these conventions for clear, readable code:
jsx1// Component names: PascalCase2function UserProfile() { ... }3function NavigationMenu() { ... }45// Props: camelCase6<UserProfile userName="alice" isActive={true} />78// Event handlers: handle + Event9function handleClick() { ... }10function handleSubmit() { ... }1112// Event handler props: on + Event13<Button onClick={handleClick} onHover={handleHover} />1415// Boolean props: is/has/can/should prefix16<Modal isOpen={true} hasCloseButton={false} />
Container vs Presentational
A classic pattern for separating concerns:
Presentational Components - Focus on UI:
jsx1// Only cares about how things look2function UserCard({ name, avatar, bio, onFollow }) {3 return (4 <div className="user-card">5 <img src={avatar} alt={name} />6 <h3>{name}</h3>7 <p>{bio}</p>8 <button onClick={onFollow}>Follow</button>9 </div>10 );11}
Container Components - Focus on logic:
jsx1// Only cares about how things work2function UserCardContainer({ userId }) {3 const [user, setUser] = useState(null);45 useEffect(() => {6 fetchUser(userId).then(setUser);7 }, [userId]);89 const handleFollow = () => {10 followUser(userId);11 };1213 if (!user) return <Loading />;1415 return (16 <UserCard17 name={user.name}18 avatar={user.avatar}19 bio={user.bio}20 onFollow={handleFollow}21 />22 );23}
Note: With hooks, this separation is less strict. Logic can be extracted to custom hooks instead.
Lifting State Up
When siblings need to share state, lift it to their common parent:
jsx1// BEFORE: Each input manages its own state2function TemperatureInput() {3 const [value, setValue] = useState('');4 return <input value={value} onChange={e => setValue(e.target.value)} />;5}67// AFTER: Parent manages shared state8function TemperatureCalculator() {9 const [celsius, setCelsius] = useState('');10 const [fahrenheit, setFahrenheit] = useState('');1112 const handleCelsiusChange = (value) => {13 setCelsius(value);14 setFahrenheit(value ? ((parseFloat(value) * 9/5) + 32).toFixed(1) : '');15 };1617 const handleFahrenheitChange = (value) => {18 setFahrenheit(value);19 setCelsius(value ? ((parseFloat(value) - 32) * 5/9).toFixed(1) : '');20 };2122 return (23 <div>24 <TemperatureInput25 scale="Celsius"26 value={celsius}27 onChange={handleCelsiusChange}28 />29 <TemperatureInput30 scale="Fahrenheit"31 value={fahrenheit}32 onChange={handleFahrenheitChange}33 />34 </div>35 );36}3738function TemperatureInput({ scale, value, onChange }) {39 return (40 <label>41 {scale}:42 <input43 value={value}44 onChange={e => onChange(e.target.value)}45 />46 </label>47 );48}
Prop Validation with PropTypes
Runtime type checking for development:
jsx1import PropTypes from 'prop-types';23function UserCard({ name, age, email, role, onSelect }) {4 return (5 <div onClick={() => onSelect(email)}>6 <h3>{name}</h3>7 <p>Age: {age}</p>8 <p>Role: {role}</p>9 </div>10 );11}1213UserCard.propTypes = {14 name: PropTypes.string.isRequired,15 age: PropTypes.number,16 email: PropTypes.string.isRequired,17 role: PropTypes.oneOf(['admin', 'user', 'guest']),18 onSelect: PropTypes.func.isRequired,19};2021UserCard.defaultProps = {22 age: 0,23 role: 'user',24};
Common PropTypes:
PropTypes.stringPropTypes.numberPropTypes.boolPropTypes.funcPropTypes.arrayPropTypes.objectPropTypes.node(anything renderable)PropTypes.element(React element)PropTypes.oneOf(['a', 'b'])PropTypes.arrayOf(PropTypes.string)PropTypes.shape({ name: PropTypes.string })
TypeScript Props (Brief Introduction)
TypeScript provides compile-time type checking:
tsx1// Define props interface2interface ButtonProps {3 variant: 'primary' | 'secondary' | 'danger';4 size?: 'small' | 'medium' | 'large';5 disabled?: boolean;6 onClick: () => void;7 children: React.ReactNode;8}910// Use interface in component11function Button({12 variant,13 size = 'medium',14 disabled = false,15 onClick,16 children17}: ButtonProps) {18 return (19 <button20 className={`btn btn-${variant} btn-${size}`}21 disabled={disabled}22 onClick={onClick}23 >24 {children}25 </button>26 );27}2829// TypeScript catches errors at compile time30<Button variant="invalid">Click</Button> // Error!31<Button variant="primary">Click</Button> // Missing onClick - Error!
Slot Pattern with Children
Create flexible layouts using children as slots:
jsx1function Card({ header, children, footer }) {2 return (3 <div className="card">4 {header && <div className="card-header">{header}</div>}5 <div className="card-body">{children}</div>6 {footer && <div className="card-footer">{footer}</div>}7 </div>8 );9}1011// Usage with named slots12<Card13 header={<h2>Card Title</h2>}14 footer={<Button>Action</Button>}15>16 <p>Card content goes here</p>17</Card>
Compound Components Pattern (Preview)
Group related components that work together:
jsx1// Parent provides context2function Tabs({ children, defaultValue }) {3 const [activeTab, setActiveTab] = useState(defaultValue);45 return (6 <TabsContext.Provider value={{ activeTab, setActiveTab }}>7 <div className="tabs">{children}</div>8 </TabsContext.Provider>9 );10}1112// Children consume context13function TabList({ children }) {14 return <div className="tab-list">{children}</div>;15}1617function Tab({ value, children }) {18 const { activeTab, setActiveTab } = useContext(TabsContext);19 return (20 <button21 className={activeTab === value ? 'active' : ''}22 onClick={() => setActiveTab(value)}23 >24 {children}25 </button>26 );27}2829function TabPanel({ value, children }) {30 const { activeTab } = useContext(TabsContext);31 return activeTab === value ? <div>{children}</div> : null;32}3334// Attach children to parent35Tabs.List = TabList;36Tabs.Tab = Tab;37Tabs.Panel = TabPanel;3839// Clean, readable usage40<Tabs defaultValue="tab1">41 <Tabs.List>42 <Tabs.Tab value="tab1">First</Tabs.Tab>43 <Tabs.Tab value="tab2">Second</Tabs.Tab>44 </Tabs.List>45 <Tabs.Panel value="tab1">First panel content</Tabs.Panel>46 <Tabs.Panel value="tab2">Second panel content</Tabs.Panel>47</Tabs>
We'll explore this pattern in depth in the Advanced Patterns topic.
Component Best Practices
1. Keep Components Small and Focused
jsx1// Too much in one component2function UserDashboard({ user }) {3 // 200 lines of mixed concerns...4}56// Better: Split into focused components7function UserDashboard({ user }) {8 return (9 <div>10 <UserHeader user={user} />11 <UserStats userId={user.id} />12 <UserActivityFeed userId={user.id} />13 <UserSettings user={user} />14 </div>15 );16}
2. Use Meaningful Names
jsx1// Unclear2function Item({ d }) {3 return <div>{d.n}</div>;4}56// Clear7function ProductCard({ product }) {8 return <div>{product.name}</div>;9}
3. Extract Complex Conditions
jsx1// Hard to read2{user && user.isActive && !user.isBlocked && user.role === 'admin' && <AdminPanel />}34// Better: Extract to variable or function5const canAccessAdmin = user?.isActive && !user?.isBlocked && user?.role === 'admin';6{canAccessAdmin && <AdminPanel />}
4. Avoid Prop Drilling
When props pass through many layers, consider:
- Context API (covered in Topic 6)
- State management libraries
- Component composition
jsx1// Prop drilling (avoid)2<App user={user}>3 <Layout user={user}>4 <Sidebar user={user}>5 <UserMenu user={user} />6 </Sidebar>7 </Layout>8</App>910// Better: Use context or composition11<UserProvider user={user}>12 <App>13 <Layout>14 <Sidebar>15 <UserMenu /> {/* Gets user from context */}16 </Sidebar>17 </Layout>18 </App>19</UserProvider>
Summary
Key patterns for clean React components:
- Organize files consistently (by feature or type)
- Name clearly - PascalCase components, camelCase props
- Separate concerns - presentational vs container (or use hooks)
- Lift state when siblings need to share
- Validate props with PropTypes or TypeScript
- Use slots for flexible composition
- Keep focused - small components with single responsibility
Next, let's put these patterns into practice by building a reusable card component system!