Modern React Development Patterns and Best Practices
React continues to evolve, and with it, the way we build applications. Let’s explore the most effective patterns and practices for React development in 2025.
Server Components vs Client Components
// app/page.tsx - Server Component
export default async function Page() {
const data = await fetchData(); // Server-side data fetching
return (
<main>
<ClientSideComponent initialData={data} />
</main>
);
}
// components/ClientSideComponent.tsx - Client Component
'use client';
export default function ClientSideComponent({ initialData }) {
const [data, setData] = useState(initialData);
// Client-side interactivity
}
Advanced Hook Patterns
1. Custom Hooks for Reusability
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue] as const;
}
2. Composition Pattern
interface ButtonProps {
variant: 'primary' | 'secondary';
children: React.ReactNode;
}
const Button = ({ variant, children, ...props }: ButtonProps) => {
return (
<button
className={`btn btn-${variant}`}
{...props}
>
{children}
</button>
);
};
const WithLoading = <P extends object>(
Component: React.ComponentType<P>
) => {
return function WithLoadingComponent(
props: P & { loading: boolean }
) {
if (props.loading) return <Spinner />;
return <Component {...(props as P)} />;
};
};
const ButtonWithLoading = WithLoading(Button);
Performance Optimization
1. Memoization
const MemoizedComponent = memo(function MyComponent({ data }) {
return (
<div>
{/* Expensive rendering logic */}
</div>
);
}, (prevProps, nextProps) => {
return prevProps.data.id === nextProps.data.id;
});
2. Code Splitting
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}
State Management in 2025
1. Using Signals
import { signal, computed } from '@preact/signals-react';
const count = signal(0);
const doubled = computed(() => count.value * 2);
function Counter() {
return (
<button onClick={() => count.value++}>
Count: {count} (Doubled: {doubled})
</button>
);
}
2. Zustand for Global State
import create from 'zustand';
interface StoreState {
bears: number;
increasePopulation: () => void;
}
const useStore = create<StoreState>((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
}));
Testing Best Practices
import { render, screen, fireEvent } from '@testing-library/react';
test('button click increments counter', () => {
render(<Counter />);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});
Modern React Architecture
- Feature-Based Structure
src/
features/
auth/
components/
hooks/
api/
types/
dashboard/
components/
hooks/
api/
types/
shared/
components/
hooks/
utils/
- Component Design
- Atomic Design Principles
- Compound Components
- Render Props Pattern
Error Handling
function ErrorBoundary({ children }: { children: React.ReactNode }) {
return (
<ErrorBoundary fallback={<ErrorUI />}>
{children}
</ErrorBoundary>
);
}