15 mins
Modern React Patterns and Best Practices: Building Scalable Applications

Advanced React patterns, hooks, state management, and performance optimization techniques for modern web applications

Modern React Patterns and Best Practices: Building Scalable Applicationsh1

Hello! I’m Ahmet Zeybek, a full stack developer with extensive experience in building complex React applications. React has evolved tremendously since its inception, and mastering modern patterns is crucial for building maintainable, performant applications. In this comprehensive guide, I’ll share advanced React patterns and best practices that I’ve refined through years of production experience.

The Evolution of React Patternsh2

React’s ecosystem has matured significantly, moving from class components to functional components, from simple hooks to sophisticated state management solutions. Let’s explore the patterns that define modern React development.

1. Compound Components Patternh3

Problem: Creating reusable component APIs that are flexible yet intuitive.

Solution: Components that work together as a cohesive unit.

// Compound Components Pattern
interface TabsContextValue {
activeTab: string
setActiveTab: (tab: string) => void
}
const TabsContext = createContext<TabsContextValue | undefined>(undefined)
interface TabsProps {
defaultValue: string
children: React.ReactNode
}
export const Tabs: React.FC<TabsProps> & {
List: typeof TabsList
Trigger: typeof TabsTrigger
Content: typeof TabsContent
} = ({ defaultValue, children }) => {
const [activeTab, setActiveTab] = useState(defaultValue)
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
)
}
const TabsList: React.FC<{ className?: string }> = ({ className, children }) => (
<div className={`tabs-list ${className || ''}`}>{children}</div>
)
const TabsTrigger: React.FC<{ value: string; className?: string }> = ({
value,
className,
children
}) => {
const { activeTab, setActiveTab } = useContext(TabsContext)!
return (
<button
className={`tabs-trigger ${activeTab === value ? 'active' : ''} ${className || ''}`}
onClick={() => setActiveTab(value)}
>
{children}
</button>
)
}
const TabsContent: React.FC<{ value: string; className?: string }> = ({
value,
className,
children
}) => {
const { activeTab } = useContext(TabsContext)!
if (activeTab !== value) return null
return <div className={`tabs-content ${className || ''}`}>{children}</div>
}
// Compound component assignment
Tabs.List = TabsList
Tabs.Trigger = TabsTrigger
Tabs.Content = TabsContent
// Usage
<Tabs defaultValue="profile">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
<Tabs.Trigger value="billing">Billing</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="profile">Profile content...</Tabs.Content>
<Tabs.Content value="settings">Settings content...</Tabs.Content>
<Tabs.Content value="billing">Billing content...</Tabs.Content>
</Tabs>

2. Render Props Patternh3

Problem: Sharing logic between components without tight coupling.

Solution: Pass a function as a prop that renders JSX.

interface DataFetcherProps<T> {
url: string
render: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode
}
function DataFetcher<T>({ url, render }: DataFetcherProps<T>) {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true)
setError(null)
const response = await fetch(url)
const result = await response.json()
setData(result)
} catch (err) {
setError(err as Error)
} finally {
setLoading(false)
}
}
fetchData()
}, [url])
return <>{render(data, loading, error)}</>
}
// Usage with render props
<DataFetcher url="/api/user">
{(user, loading, error) => {
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
if (!user) return <div>No user data</div>
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}}
</DataFetcher>

3. Higher-Order Components (HOCs)h3

Problem: Reusing component logic across multiple components.

Solution: Functions that take a component and return an enhanced component.

// HOC for adding loading state
function withLoading<P extends object>(Component: React.ComponentType<P>) {
return function WithLoadingComponent({ isLoading, ...props }: P & { isLoading?: boolean }) {
if (isLoading) {
return <div className="loading-spinner">Loading...</div>
}
return <Component {...(props as P)} />
}
}
// HOC for authentication
function withAuth<P extends object>(Component: React.ComponentType<P>) {
return function WithAuthComponent(props: P) {
const { user, loading } = useAuth()
if (loading) return <div>Loading...</div>
if (!user) return <LoginPrompt />
return <Component {...props} />
}
}
// HOC for data fetching
function withData<P extends object>(
dataFetcher: () => Promise<any>,
dataPropName: string = 'data'
) {
return function WithDataComponent(Component: React.ComponentType<P>) {
return function WrappedComponent(props: P) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
dataFetcher()
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [])
return (
<Component
{...props}
{...{ [dataPropName]: data, loading, error }}
/>
)
}
}
}
// Usage
const UserProfileWithData = withData(
() => fetch('/api/user').then(res => res.json()),
'user'
)(UserProfile)
const AuthenticatedUserProfile = withAuth(UserProfileWithData)

Advanced Hooks Patternsh2

4. Custom Hooks for Business Logich3

Extract complex stateful logic into reusable hooks:

// Custom hook for form handling
function useForm<T extends Record<string, any>>(initialValues: T) {
const [values, setValues] = useState<T>(initialValues)
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({})
const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({})
const setValue = useCallback((field: keyof T, value: any) => {
setValues(prev => ({ ...prev, [field]: value }))
// Clear error when user starts typing
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: undefined }))
}
}, [errors])
const setTouched = useCallback((field: keyof T) => {
setTouched(prev => ({ ...prev, [field]: true }))
}, [])
const validate = useCallback(() => {
const newErrors: Partial<Record<keyof T, string>> = {}
// Validation logic here
if (!values.email) {
newErrors.email = 'Email is required'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}, [values])
const reset = useCallback(() => {
setValues(initialValues)
setErrors({})
setTouched({})
}, [initialValues])
return {
values,
errors,
touched,
setValue,
setTouched,
validate,
reset,
isValid: Object.keys(errors).length === 0
}
}
// Custom hook for API calls
function useApi<T>(url: string, options?: RequestInit) {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const fetchData = useCallback(async () => {
try {
setLoading(true)
setError(null)
const response = await fetch(url, options)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result = await response.json()
setData(result)
} catch (err) {
setError(err as Error)
} finally {
setLoading(false)
}
}, [url, options])
useEffect(() => {
fetchData()
}, [fetchData])
return { data, loading, error, refetch: fetchData }
}
// Usage
function UserForm() {
const { values, errors, setValue, validate, reset } = useForm({
name: '',
email: '',
role: 'user'
})
const { data: roles, loading: rolesLoading } = useApi('/api/roles')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (validate()) {
// Submit form
console.log('Form data:', values)
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={values.name}
onChange={(e) => setValue('name', e.target.value)}
placeholder="Name"
/>
{errors.name && <span className="error">{errors.name}</span>}
<input
value={values.email}
onChange={(e) => setValue('email', e.target.value)}
placeholder="Email"
/>
{errors.email && <span className="error">{errors.email}</span>}
<select
value={values.role}
onChange={(e) => setValue('role', e.target.value)}
>
{rolesLoading ? (
<option>Loading roles...</option>
) : (
roles?.map((role: any) => (
<option key={role.id} value={role.name}>
{role.name}
</option>
))
)}
</select>
<button type="submit">Submit</button>
<button type="button" onClick={reset}>Reset</button>
</form>
)
}

5. State Management Patternsh3

Modern state management with React Query and Zustand:

// Zustand store for global state
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface User {
id: string
name: string
email: string
role: string
}
interface AuthState {
user: User | null
isAuthenticated: boolean
login: (email: string, password: string) => Promise<void>
logout: () => void
updateProfile: (updates: Partial<User>) => Promise<void>
}
export const useAuthStore = create<AuthState>()(
devtools(
persist(
(set, get) => ({
user: null,
isAuthenticated: false,
login: async (email: string, password: string) => {
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
})
if (!response.ok) {
throw new Error('Login failed')
}
const userData = await response.json()
set({ user: userData.user, isAuthenticated: true })
} catch (error) {
throw error
}
},
logout: () => {
set({ user: null, isAuthenticated: false })
},
updateProfile: async (updates: Partial<User>) => {
const { user } = get()
if (!user) throw new Error('No user logged in')
try {
const response = await fetch(`/api/users/${user.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
})
if (!response.ok) {
throw new Error('Update failed')
}
const updatedUser = await response.json()
set({ user: updatedUser })
} catch (error) {
throw error
}
}
}),
{
name: 'auth-storage',
partialize: (state) => ({ user: state.user, isAuthenticated: state.isAuthenticated })
}
),
{ name: 'AuthStore' }
)
)
// React Query for server state
import { QueryClient, QueryClientProvider, useQuery, useMutation } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: (failureCount, error: any) => {
// Don't retry on 4xx errors
if (error?.status >= 400 && error?.status < 500) {
return false
}
return failureCount < 3
}
}
}
})
// Custom hook for posts
function usePosts() {
return useQuery({
queryKey: ['posts'],
queryFn: async () => {
const response = await fetch('/api/posts')
if (!response.ok) {
throw new Error('Failed to fetch posts')
}
return response.json()
}
})
}
// Posts component with optimistic updates
function PostsManager() {
const { data: posts, isLoading, error } = usePosts()
const createPostMutation = useMutation({
mutationFn: async (newPost: { title: string; content: string }) => {
const response = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost)
})
return response.json()
},
onMutate: async (newPost) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['posts'] })
// Snapshot previous value
const previousPosts = queryClient.getQueryData(['posts'])
// Optimistically update cache
queryClient.setQueryData(['posts'], (old: any) => ({
...old,
posts: [...old.posts, { ...newPost, id: 'temp-id', status: 'pending' }]
}))
return { previousPosts }
},
onError: (err, newPost, context) => {
// Rollback on error
if (context?.previousPosts) {
queryClient.setQueryData(['posts'], context.previousPosts)
}
},
onSettled: () => {
// Always refetch after mutation
queryClient.invalidateQueries({ queryKey: ['posts'] })
}
})
if (isLoading) return <div>Loading posts...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
{posts?.posts.map((post: any) => (
<div key={post.id} className={post.status === 'pending' ? 'pending' : ''}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
))}
<form onSubmit={(e) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
createPostMutation.mutate({
title: formData.get('title') as string,
content: formData.get('content') as string
})
}}>
<input name="title" placeholder="Title" />
<textarea name="content" placeholder="Content" />
<button type="submit">Create Post</button>
</form>
</div>
)
}
// App wrapper
function App() {
return (
<QueryClientProvider client={queryClient}>
<PostsManager />
</QueryClientProvider>
)
}

Performance Optimization Patternsh2

6. Virtualization for Large Listsh3

Handle thousands of items efficiently:

import { useVirtualizer } from '@tanstack/react-virtual'
// Virtual scrolling for large lists
function VirtualizedList({ items }: { items: any[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // Estimated height of each row
overscan: 5 // Render 5 extra items outside visible area
})
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative'
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => {
const item = items[virtualItem.index]
return (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`
}}
>
<div className="list-item">
<h4>{item.title}</h4>
<p>{item.description}</p>
</div>
</div>
)
})}
</div>
</div>
)
}

7. Memoization Strategiesh3

Prevent unnecessary re-renders:

import { memo, useMemo, useCallback } from 'react'
// Component memoization
const ExpensiveComponent = memo(({ data, onClick }: { data: any; onClick: (id: string) => void }) => {
console.log('ExpensiveComponent rendered')
return (
<div onClick={() => onClick(data.id)}>
{data.title}
</div>
)
})
// Expensive calculation memoization
function DataProcessor({ rawData }: { rawData: any[] }) {
const processedData = useMemo(() => {
console.log('Processing data...')
return rawData.map(item => ({
...item,
processedAt: new Date(),
hash: generateHash(item)
}))
}, [rawData]) // Only recalculate when rawData changes
return <div>{processedData.length} items processed</div>
}
// Callback memoization
function ParentComponent() {
const [count, setCount] = useState(0)
const handleClick = useCallback((id: string) => {
console.log('Clicked item:', id)
setCount(prev => prev + 1)
}, []) // Empty dependency array - never changes
return (
<ChildComponent onItemClick={handleClick} />
)
}

Error Boundary Patternsh2

8. Advanced Error Boundariesh3

Handle errors gracefully across component trees:

interface ErrorBoundaryState {
hasError: boolean
error: Error | null
errorInfo: React.ErrorInfo | null
}
interface ErrorBoundaryProps {
fallback?: React.ComponentType<{ error: Error; resetError: () => void }>
onError?: (error: Error, errorInfo: React.ErrorInfo) => void
}
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props)
this.state = { hasError: false, error: null, errorInfo: null }
}
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
this.setState({ errorInfo })
// Log error to monitoring service
this.props.onError?.(error, errorInfo)
// Report to error tracking service
if (window.gtag) {
gtag('event', 'exception', {
description: error.message,
fatal: false
})
}
}
resetError = () => {
this.setState({ hasError: false, error: null, errorInfo: null })
}
render() {
if (this.state.hasError) {
if (this.props.fallback) {
const FallbackComponent = this.props.fallback
return <FallbackComponent error={this.state.error!} resetError={this.resetError} />
}
return (
<div className="error-fallback">
<h2>Something went wrong</h2>
<p>{this.state.error?.message}</p>
<button onClick={this.resetError}>Try again</button>
</div>
)
}
return this.props.children
}
}
// Custom fallback component
const ErrorFallback: React.FC<{ error: Error; resetError: () => void }> = ({
error,
resetError
}) => (
<div className="error-fallback">
<h2>Application Error</h2>
<p>A critical error occurred: {error.message}</p>
<details>
<summary>Error Details</summary>
<pre>{error.stack}</pre>
</details>
<button onClick={resetError}>Reload Application</button>
</div>
)
// Usage with different fallback strategies
function App() {
return (
<ErrorBoundary
onError={(error, errorInfo) => {
// Log to monitoring service
console.error('App Error:', error, errorInfo)
}}
>
<ErrorBoundary fallback={ErrorFallback}>
<MainApplication />
</ErrorBoundary>
</ErrorBoundary>
)
}

Testing Patternsh2

9. Modern Testing Strategiesh3

Comprehensive testing with React Testing Library and Jest:

import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
// Test utilities
const createTestQueryClient = () => new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false }
}
})
const renderWithProviders = (ui: React.ReactElement) => {
const queryClient = createTestQueryClient()
return render(
<QueryClientProvider client={queryClient}>
{ui}
</QueryClientProvider>
)
}
// Component test
describe('UserForm', () => {
const user = userEvent.setup()
it('should submit form with valid data', async () => {
const mockSubmit = jest.fn()
renderWithProviders(<UserForm onSubmit={mockSubmit} />)
// Fill form
await user.type(screen.getByLabelText(/name/i), 'John Doe')
await user.type(screen.getByLabelText(/email/i), 'john@example.com')
// Submit form
await user.click(screen.getByRole('button', { name: /submit/i }))
// Assert
await waitFor(() => {
expect(mockSubmit).toHaveBeenCalledWith({
name: 'John Doe',
email: 'john@example.com'
})
})
})
it('should show validation errors', async () => {
renderWithProviders(<UserForm />)
// Try to submit empty form
await user.click(screen.getByRole('button', { name: /submit/i }))
// Check for validation errors
expect(screen.getByText(/name is required/i)).toBeInTheDocument()
expect(screen.getByText(/email is required/i)).toBeInTheDocument()
})
})
// Hook testing
describe('useForm', () => {
it('should initialize with default values', () => {
const { result } = renderHook(() =>
useForm({ name: '', email: '' })
)
expect(result.current.values).toEqual({ name: '', email: '' })
expect(result.current.errors).toEqual({})
})
it('should validate required fields', () => {
const { result } = renderHook(() =>
useForm({ name: '', email: '' })
)
const isValid = result.current.validate()
expect(isValid).toBe(false)
expect(result.current.errors.name).toBe('Name is required')
expect(result.current.errors.email).toBe('Email is required')
})
})
// Integration testing with mocked API
describe('PostsManager Integration', () => {
beforeEach(() => {
// Mock API responses
fetchMock.mockResponse(JSON.stringify({
posts: [
{ id: '1', title: 'Test Post', content: 'Test content' }
]
}))
})
afterEach(() => {
fetchMock.resetMocks()
})
it('should fetch and display posts', async () => {
renderWithProviders(<PostsManager />)
// Wait for data to load
await waitFor(() => {
expect(screen.getByText('Test Post')).toBeInTheDocument()
})
// Check loading state is removed
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument()
})
})

Accessibility Patternsh2

10. Building Accessible React Componentsh3

Ensure your components work for all users:

// Accessible form components
const AccessibleInput: React.FC<{
label: string
name: string
type?: string
error?: string
required?: boolean
}> = ({ label, name, type = 'text', error, required, children, ...props }) => {
const id = useId()
const errorId = `${id}-error`
return (
<div className="form-group">
<label htmlFor={id} className="form-label">
{label}
{required && <span aria-label="required">*</span>}
</label>
<input
id={id}
name={name}
type={type}
aria-required={required}
aria-invalid={!!error}
aria-describedby={error ? errorId : undefined}
className={`form-input ${error ? 'error' : ''}`}
{...props}
/>
{error && (
<div id={errorId} className="form-error" role="alert">
{error}
</div>
)}
</div>
)
}
// Accessible modal
const AccessibleModal: React.FC<{
isOpen: boolean
onClose: () => void
title: string
children: React.ReactNode
}> = ({ isOpen, onClose, title, children }) => {
const modalRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (isOpen && modalRef.current) {
// Focus management
const focusableElements = modalRef.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
)
const firstElement = focusableElements[0] as HTMLElement
firstElement?.focus()
// Trap focus within modal
const handleTabKey = (e: KeyboardEvent) => {
if (e.key === 'Tab') {
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault()
lastElement?.focus()
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault()
firstElement?.focus()
}
}
}
document.addEventListener('keydown', handleTabKey)
return () => document.removeEventListener('keydown', handleTabKey)
}
}, [isOpen])
if (!isOpen) return null
return (
<div
className="modal-overlay"
onClick={onClose}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<div
ref={modalRef}
className="modal-content"
onClick={(e) => e.stopPropagation()}
>
<header className="modal-header">
<h2 id="modal-title">{title}</h2>
<button
onClick={onClose}
className="modal-close"
aria-label="Close modal"
>
×
</button>
</header>
<main className="modal-body">
{children}
</main>
</div>
</div>
)
}
// Accessible data table
const AccessibleTable: React.FC<{
data: any[]
columns: Array<{ key: string; label: string; sortable?: boolean }>
}> = ({ data, columns }) => {
const [sortConfig, setSortConfig] = useState<{ key: string; direction: 'asc' | 'desc' } | null>(null)
const sortedData = useMemo(() => {
if (!sortConfig) return data
return [...data].sort((a, b) => {
const aValue = a[sortConfig.key]
const bValue = b[sortConfig.key]
if (aValue < bValue) return sortConfig.direction === 'asc' ? -1 : 1
if (aValue > bValue) return sortConfig.direction === 'asc' ? 1 : -1
return 0
})
}, [data, sortConfig])
return (
<table role="table" className="data-table">
<thead>
<tr>
{columns.map((column) => (
<th key={column.key} role="columnheader">
{column.sortable ? (
<button
onClick={() => {
setSortConfig({
key: column.key,
direction: sortConfig?.key === column.key && sortConfig.direction === 'asc'
? 'desc'
: 'asc'
})
}}
aria-sort={
sortConfig?.key === column.key
? sortConfig.direction === 'asc' ? 'ascending' : 'descending'
: 'none'
}
className="sortable-header"
>
{column.label}
<span className="sort-indicator" aria-hidden="true">
{sortConfig?.key === column.key
? sortConfig.direction === 'asc' ? '↑' : '↓'
: '↕'
}
</span>
</button>
) : (
column.label
)}
</th>
))}
</tr>
</thead>
<tbody>
{sortedData.map((row, index) => (
<tr key={row.id || index} role="row">
{columns.map((column) => (
<td key={column.key} role="cell">
{row[column.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
)
}

Conclusionh2

Modern React development is about choosing the right patterns for your specific use case. The patterns I’ve shared here form a solid foundation for building scalable, maintainable React applications.

Key takeaways:

  • Compound Components for flexible component APIs
  • Custom Hooks for reusable business logic
  • State Management with React Query and Zustand
  • Performance Optimization with virtualization and memoization
  • Error Boundaries for graceful error handling
  • Accessibility as a first-class concern

Remember, patterns are tools, not rigid rules. Always measure the impact of your choices and be willing to refactor as requirements evolve.

What React patterns have worked best for your projects? Which challenges are you facing with modern React development? Share your experiences!

Further Readingh2


This post reflects my experience as of October 2025. React ecosystem evolves rapidly, so always check for the latest patterns and best practices.