From bf84903269f95812f3f633203ff495292e7f4c2f Mon Sep 17 00:00:00 2001 From: Pankaj singh <114842051+PankajSingh34@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:10:29 +0530 Subject: [PATCH 1/2] feat: Add React 19 features, TypeScript examples, and modern patterns - Added React 19 new features: use() hook, React Compiler, Server Components - Included TypeScript examples for better type safety - Added performance optimization patterns with useMemo, useCallback, React.memo - Implemented security best practices and accessibility guidelines - Added advanced patterns: Compound Components, Render Props - Updated testing strategies with React Testing Library - Modernized lifecycle methods with hooks equivalents - Enhanced table of contents and improved organization - Updated metadata for 2026 edition --- README.md | 3279 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 3274 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 783fdb5..ca59eed 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,60 @@ --- -title: "React Js Coding Interview Questions & Answers 2025 - Asked by TOP Companies" -description: "Crack your React interview! Explore top React Js coding interview questions with answers. Covers hooks, lifecycle, state, and performance tips." -githubPath: "https://github.com/Vasu7389/ReactJs-Interview-Question" +title: "React Js Interview Questions & Answers 2026 - Modern React, TypeScript & Best Practices" +description: "Master React interviews! Comprehensive guide with 90+ questions covering React 19, Server Components, TypeScript, testing, performance, security, and advanced patterns." +githubPath: "https://github.com/PankajSingh34/ReactJs-Interview-Question" --- - Updated Mar 29, 2025 + Updated January 12, 2026 -Here you'll find the top 50+ React js coding test questions and answers for freshers, beginners, frontend developers, junior developers as well as for experienced developers which might help you cracking your next interview. +Here you'll find **90+ comprehensive React.js interview questions and answers** covering everything from fundamentals to advanced patterns. Perfect for freshers, experienced developers, and everyone in between preparing for React interviews at top companies. + +## What's New in 2026 Edition? + +โœจ **React 19 Features** - Latest hooks, Server Components, and React Compiler +๐Ÿ”ท **TypeScript Integration** - Advanced patterns and type-safe React development +๐Ÿงช **Modern Testing** - React Testing Library, Jest, and accessibility testing +๐Ÿš€ **Performance** - Advanced optimization techniques and best practices +๐Ÿ”’ **Security & A11y** - Security vulnerabilities and accessibility implementation +๐Ÿ“ **Advanced Patterns** - Compound components, render props, and modern architecture + +## What's New in 2026 Edition? + +โœจ **React 19 Features** - Latest hooks, Server Components, and React Compiler +๐Ÿ”ท **TypeScript Integration** - Advanced patterns and type-safe React development +๐Ÿงช **Modern Testing** - React Testing Library, Jest, and accessibility testing +๐Ÿš€ **Performance** - Advanced optimization techniques and best practices +๐Ÿ”’ **Security & A11y** - Security vulnerabilities and accessibility implementation +๐Ÿ“ **Advanced Patterns** - Compound components, render props, and modern architecture + +## ๐Ÿ“š Table of Contents + +1. **[React Fundamentals](#react-js-interview-questions)** (Questions 1-52) + - Basic concepts, components, hooks, lifecycle methods + - State management, props, JSX, Virtual DOM + +2. **[React 19 & Modern Features](#react-19-features--modern-react)** (Questions 79-83) + - New `use()` hook, React Compiler, Server Components + - App Router, Suspense, and concurrent features + +3. **[Performance & TypeScript](#performance-optimization--typescript)** (Questions 84-85) + - Advanced memoization, useMemo, useCallback + - TypeScript patterns, generic components, type safety + +4. **[Testing & Quality](#react-testing--best-practices)** (Question 86) + - React Testing Library, custom hook testing + - Component testing, mocking, accessibility testing + +5. **[Security & Accessibility](#react-security--accessibility)** (Questions 87-88) + - XSS prevention, authentication, CSRF protection + - Screen readers, ARIA, keyboard navigation + +6. **[Advanced Patterns](#advanced-react-patterns)** (Questions 89-90) + - Compound components, render props + - Custom hooks vs render props comparison + +7. **[Coding Challenges](#react-js-coding-test-questions-and-qnswers)** (Questions 53-78) + - Practical implementations, scenario-based problems + - Real-world examples and best practices ## ReactJs @@ -18,6 +66,8 @@ Looking to expand your knowledge on Javascript as well? Check out our comprehens \*Discover the answers by clicking on the questions. +> **๐Ÿ’ก Important Note for 2026:** This guide has been extensively updated with modern React practices. While legacy patterns (like deprecated lifecycle methods) are mentioned for completeness, we strongly recommend using the modern equivalents (hooks, TypeScript, etc.) shown throughout this guide. + ## React Js Interview Questions
@@ -2573,3 +2623,3222 @@ This question evaluates: โœ… Efficient re-renders to prevent unnecessary API calls
+ +--- + +## React 19 Features & Modern React + +
+ +

79. What are the new features introduced in React 19?

+
+ +React 19 introduces several powerful features that enhance developer experience and application performance: + +**New Hooks:** + +- **`use()` Hook**: A new hook that can consume promises and context +- **`useFormStatus()`**: For handling form submission states +- **`useActionState()`**: For managing server actions state + +**React Compiler (Automatic Memoization):** +- Automatically optimizes your components without manual memoization +- Eliminates the need for `useMemo`, `useCallback`, and `React.memo` in many cases + +**Server Components Enhancements:** +- Improved Server Components with better hydration +- Enhanced streaming capabilities +- Better integration with frameworks like Next.js + +**Actions and Forms:** +- Native support for Server Actions +- Improved form handling with automatic pending states +- Built-in error boundaries for async operations + +```tsx +// React 19 - use() hook example +import { use } from 'react'; + +interface User { + id: number; + name: string; +} + +function UserProfile({ userPromise }: { userPromise: Promise }) { + const user = use(userPromise); + + return
Welcome, {user.name}!
; +} + +// React 19 - useFormStatus() example +import { useFormStatus } from 'react-dom'; + +function SubmitButton() { + const { pending } = useFormStatus(); + + return ( + + ); +} +``` + +
+ +
+ +

80. Explain React's new `use()` hook and provide examples.

+
+ +The `use()` hook is a new primitive in React 19 that can consume promises and context. Unlike other hooks, `use()` can be called conditionally. + +**Key Features:** +- Can consume Promises and Context +- Can be called conditionally (unlike other hooks) +- Suspends the component while waiting for Promise resolution + +```tsx +import { use, Suspense } from 'react'; + +// TypeScript interfaces +interface User { + id: number; + name: string; + email: string; +} + +// Consuming a Promise with use() +function UserData({ userPromise }: { userPromise: Promise }) { + const user = use(userPromise); + + return ( +
+

{user.name}

+

{user.email}

+
+ ); +} + +// Consuming Context with use() +import { createContext } from 'react'; + +const ThemeContext = createContext<'light' | 'dark'>('light'); + +function ThemeDisplay() { + const theme = use(ThemeContext); + + return
Current theme: {theme}
; +} + +// Usage with conditional logic +function ConditionalData({ shouldFetch, dataPromise }: { + shouldFetch: boolean; + dataPromise: Promise; +}) { + if (!shouldFetch) { + return
Not fetching data
; + } + + // This is allowed with use() but not with other hooks + const data = use(dataPromise); + return
{data.content}
; +} + +// App component +function App() { + const userPromise = fetch('/api/user').then(res => res.json()); + + return ( + Loading...}> + + + ); +} +``` + +
+ +
+ +

81. What is the React Compiler and how does it improve performance?

+
+ +The React Compiler is an experimental feature in React 19 that automatically optimizes React components by adding memoization where beneficial, eliminating the need for manual optimization with `useMemo`, `useCallback`, and `React.memo`. + +**Key Benefits:** +- **Automatic Optimization**: No manual memoization needed +- **Better Performance**: Prevents unnecessary re-renders automatically +- **Developer Experience**: Focus on logic, not performance optimization +- **Gradual Adoption**: Can be enabled incrementally + +```tsx +// Before React Compiler (Manual optimization) +import { useMemo, useCallback, memo } from 'react'; + +interface User { + id: number; + name: string; +} + +interface TodoListProps { + users: User[]; + onUserSelect: (userId: number) => void; +} + +const TodoList = memo(({ users, onUserSelect }: TodoListProps) => { + const expensiveComputation = useMemo(() => { + return users.filter(user => user.name.length > 5) + .map(user => ({ ...user, displayName: user.name.toUpperCase() })); + }, [users]); + + const handleUserClick = useCallback((userId: number) => { + onUserSelect(userId); + }, [onUserSelect]); + + return ( +
+ {expensiveComputation.map(user => ( +
handleUserClick(user.id)}> + {user.displayName} +
+ ))} +
+ ); +}); + +// After React Compiler (Automatic optimization) +function TodoList({ users, onUserSelect }: TodoListProps) { + // React Compiler automatically memoizes this computation + const expensiveComputation = users.filter(user => user.name.length > 5) + .map(user => ({ ...user, displayName: user.name.toUpperCase() })); + + // React Compiler automatically memoizes this callback + const handleUserClick = (userId: number) => { + onUserSelect(userId); + }; + + return ( +
+ {expensiveComputation.map(user => ( +
handleUserClick(user.id)}> + {user.displayName} +
+ ))} +
+ ); +} +``` + +**How to enable React Compiler:** +```bash +# Install React Compiler +npm install react-compiler-runtime +npm install --save-dev babel-plugin-react-compiler +``` + +
+ +
+ +

82. What are React Server Components and how do they work?

+
+ +React Server Components (RSC) are components that run on the server and send their rendered output to the client. They enable better performance by reducing the JavaScript bundle size and improving initial page load. + +**Key Benefits:** +- **Zero Bundle Size**: Server Components don't add to client bundle +- **Direct Server Access**: Can directly access databases, file systems +- **Automatic Code Splitting**: Only client components are bundled +- **SEO Friendly**: Pre-rendered on server + +```tsx +// Server Component (runs on server) +import { db } from '@/lib/database'; + +interface BlogPost { + id: string; + title: string; + content: string; + author: string; +} + +// This component runs on the server +async function BlogList() { + // Direct database access - only runs on server + const posts: BlogPost[] = await db.posts.findMany({ + orderBy: { createdAt: 'desc' } + }); + + return ( +
+

Latest Blog Posts

+ {posts.map(post => ( +
+

{post.title}

+

By {post.author}

+
{post.content.substring(0, 200)}...
+
+ ))} +
+ ); +} + +// Client Component (runs in browser) +'use client'; + +import { useState } from 'react'; + +interface SearchBarProps { + onSearch: (query: string) => void; +} + +function SearchBar({ onSearch }: SearchBarProps) { + const [query, setQuery] = useState(''); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSearch(query); + }; + + return ( +
+ setQuery(e.target.value)} + placeholder="Search posts..." + /> + +
+ ); +} + +// Layout combining Server and Client Components +function BlogPage() { + return ( +
+ {/* This runs on the client for interactivity */} + console.log(query)} /> + + {/* This runs on the server for data fetching */} + +
+ ); +} +``` + +**File Structure Example:** +``` +app/ +โ”œโ”€โ”€ layout.tsx // Server Component +โ”œโ”€โ”€ page.tsx // Server Component +โ”œโ”€โ”€ blog/ +โ”‚ โ”œโ”€โ”€ page.tsx // Server Component +โ”‚ โ””โ”€โ”€ search.tsx // Client Component ('use client') +โ””โ”€โ”€ components/ + โ”œโ”€โ”€ header.tsx // Server Component + โ””โ”€โ”€ interactive.tsx // Client Component +``` + +
+ +
+ +

83. Explain the App Router in Next.js 13+ and its benefits.

+
+ +The App Router is a new routing system in Next.js 13+ that leverages React Server Components and provides a more intuitive file-based routing system. + +**Key Features:** +- **File-based routing** with `app/` directory +- **Layouts** and **Templates** for shared UI +- **Server Components** by default +- **Streaming** and **Suspense** support +- **Parallel Routes** and **Intercepting Routes** + +```tsx +// app/layout.tsx - Root Layout (Server Component) +import './globals.css'; + +export const metadata = { + title: 'My App', + description: 'A Next.js 13+ application', +}; + +interface RootLayoutProps { + children: React.ReactNode; +} + +export default function RootLayout({ children }: RootLayoutProps) { + return ( + + +
+ +
+
{children}
+ + + + ); +} + +// app/page.tsx - Home Page (Server Component) +export default function HomePage() { + return ( +
+

Welcome to My App

+

This is a server component!

+
+ ); +} + +// app/blog/layout.tsx - Nested Layout +interface BlogLayoutProps { + children: React.ReactNode; +} + +export default function BlogLayout({ children }: BlogLayoutProps) { + return ( +
+ +
+ {children} +
+
+ ); +} + +// app/blog/[slug]/page.tsx - Dynamic Route +interface BlogPostPageProps { + params: { slug: string }; +} + +export default async function BlogPostPage({ params }: BlogPostPageProps) { + // This runs on the server + const post = await fetch(`https://api.example.com/posts/${params.slug}`) + .then(res => res.json()); + + return ( +
+

{post.title}

+
{post.content}
+
+ ); +} + +// app/blog/[slug]/loading.tsx - Loading UI +export default function Loading() { + return ( +
+

Loading blog post...

+
+
+ ); +} + +// app/blog/[slug]/error.tsx - Error UI +'use client'; + +interface ErrorPageProps { + error: Error; + reset: () => void; +} + +export default function Error({ error, reset }: ErrorPageProps) { + return ( +
+

Something went wrong!

+

{error.message}

+ +
+ ); +} +``` + +**Directory Structure:** +``` +app/ +โ”œโ”€โ”€ layout.tsx // Root layout +โ”œโ”€โ”€ page.tsx // Home page +โ”œโ”€โ”€ loading.tsx // Global loading +โ”œโ”€โ”€ error.tsx // Global error +โ”œโ”€โ”€ globals.css // Global styles +โ”œโ”€โ”€ blog/ +โ”‚ โ”œโ”€โ”€ layout.tsx // Blog layout +โ”‚ โ”œโ”€โ”€ page.tsx // Blog listing +โ”‚ โ”œโ”€โ”€ [slug]/ +โ”‚ โ”‚ โ”œโ”€โ”€ page.tsx // Individual blog post +โ”‚ โ”‚ โ”œโ”€โ”€ loading.tsx // Post loading +โ”‚ โ”‚ โ””โ”€โ”€ error.tsx // Post error +โ”‚ โ””โ”€โ”€ create/ +โ”‚ โ””โ”€โ”€ page.tsx // Create post +โ””โ”€โ”€ api/ + โ””โ”€โ”€ posts/ + โ””โ”€โ”€ route.ts // API endpoint +``` + +
+ +--- + +## Performance Optimization & TypeScript + +
+ +

84. Advanced Performance Optimization: When and how to use React.memo, useMemo, and useCallback?

+
+ +Understanding when and how to use these optimization techniques is crucial for building performant React applications. + +**React.memo - Component Memoization:** +```tsx +// TypeScript interface for props +interface UserCardProps { + user: { + id: number; + name: string; + email: string; + }; + onEdit: (userId: number) => void; +} + +// Memoized component - only re-renders when props change +const UserCard = React.memo(({ user, onEdit }: UserCardProps) => { + console.log('UserCard rendered for:', user.name); + + return ( +
+

{user.name}

+

{user.email}

+ +
+ ); +}); + +// Custom comparison function for React.memo +const UserCardWithCustomComparison = React.memo( + ({ user, onEdit }: UserCardProps) => { + return ( +
+

{user.name}

+

{user.email}

+ +
+ ); + }, + (prevProps, nextProps) => { + // Only re-render if user data actually changed + return ( + prevProps.user.id === nextProps.user.id && + prevProps.user.name === nextProps.user.name && + prevProps.user.email === nextProps.user.email + ); + } +); +``` + +**useMemo - Value Memoization:** +```tsx +interface Product { + id: number; + name: string; + price: number; + category: string; +} + +interface ProductListProps { + products: Product[]; + filter: string; + sortBy: 'name' | 'price'; +} + +function ProductList({ products, filter, sortBy }: ProductListProps) { + // Expensive computation - only runs when dependencies change + const filteredAndSortedProducts = useMemo(() => { + console.log('Computing filtered and sorted products'); + + return products + .filter(product => + product.name.toLowerCase().includes(filter.toLowerCase()) || + product.category.toLowerCase().includes(filter.toLowerCase()) + ) + .sort((a, b) => { + if (sortBy === 'name') { + return a.name.localeCompare(b.name); + } + return a.price - b.price; + }); + }, [products, filter, sortBy]); + + // Memoized calculation + const totalValue = useMemo(() => { + return filteredAndSortedProducts.reduce( + (sum, product) => sum + product.price, + 0 + ); + }, [filteredAndSortedProducts]); + + return ( +
+

Products (Total: ${totalValue.toFixed(2)})

+ {filteredAndSortedProducts.map(product => ( +
+

{product.name}

+

${product.price}

+
+ ))} +
+ ); +} +``` + +**useCallback - Function Memoization:** +```tsx +interface TodoListProps { + todos: Array<{ + id: number; + text: string; + completed: boolean; + }>; +} + +function TodoList({ todos }: TodoListProps) { + const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all'); + + // Memoized callback - prevents child re-renders + const handleToggleTodo = useCallback((todoId: number) => { + setTodos(prevTodos => + prevTodos.map(todo => + todo.id === todoId + ? { ...todo, completed: !todo.completed } + : todo + ) + ); + }, []); // Empty dependency array since setTodos is stable + + // Memoized callback with dependencies + const handleFilterChange = useCallback((newFilter: 'all' | 'active' | 'completed') => { + console.log('Filter changed to:', newFilter); + setFilter(newFilter); + }, []); + + // Memoized filtered todos + const filteredTodos = useMemo(() => { + switch (filter) { + case 'active': + return todos.filter(todo => !todo.completed); + case 'completed': + return todos.filter(todo => todo.completed); + default: + return todos; + } + }, [todos, filter]); + + return ( +
+ + {filteredTodos.map(todo => ( + + ))} +
+ ); +} + +// Child component that benefits from memoized props +const TodoItem = React.memo(({ + todo, + onToggle +}: { + todo: { id: number; text: string; completed: boolean }; + onToggle: (id: number) => void; +}) => { + console.log('TodoItem rendered:', todo.text); + + return ( +
+ onToggle(todo.id)} + /> + + {todo.text} + +
+ ); +}); +``` + +**When to use each:** + +- **React.memo**: When component re-renders frequently with same props +- **useMemo**: For expensive calculations or object creation +- **useCallback**: For functions passed as props to memoized components + +**Anti-patterns to avoid:** +```tsx +// โŒ Don't memoize everything +const OverOptimized = React.memo(() => { + const simpleValue = useMemo(() => 1 + 1, []); // Unnecessary + const simpleCallback = useCallback(() => { + console.log('hello'); + }, []); // May not be worth it + + return
{simpleValue}
; +}); + +// โœ… Only optimize when there's a performance issue +const WellOptimized = () => { + const expensiveValue = useMemo(() => { + return heavyComputation(largeDataSet); + }, [largeDataSet]); + + return
{expensiveValue}
; +}; +``` + +
+ +
+ +

85. TypeScript with React: Advanced Patterns and Best Practices

+
+ +Here are advanced TypeScript patterns for React development with proper type safety and reusability. + +**Generic Components:** +```tsx +// Generic List Component +interface ListItem { + id: string | number; +} + +interface ListProps { + items: T[]; + renderItem: (item: T) => React.ReactNode; + keyExtractor?: (item: T) => string | number; + emptyMessage?: string; +} + +function List({ + items, + renderItem, + keyExtractor = (item) => item.id, + emptyMessage = 'No items found' +}: ListProps) { + if (items.length === 0) { + return
{emptyMessage}
; + } + + return ( +
+ {items.map((item) => ( +
+ {renderItem(item)} +
+ ))} +
+ ); +} + +// Usage with different data types +interface User { + id: number; + name: string; + email: string; +} + +interface Product { + id: string; + title: string; + price: number; +} + +function App() { + const users: User[] = [ + { id: 1, name: 'John', email: 'john@example.com' } + ]; + + const products: Product[] = [ + { id: 'prod-1', title: 'Laptop', price: 999 } + ]; + + return ( +
+ ( +
+

{user.name}

+

{user.email}

+
+ )} + /> + + ( +
+

{product.title}

+

${product.price}

+
+ )} + keyExtractor={(product) => product.id} + /> +
+ ); +} +``` + +**Advanced Hook Types:** +```tsx +// Custom hook with proper TypeScript +interface UseApiResult { + data: T | null; + loading: boolean; + error: string | null; + refetch: () => Promise; +} + +function useApi(url: string, options?: RequestInit): UseApiResult { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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 instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + }, [url, options]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { data, loading, error, refetch: fetchData }; +} + +// Usage with typed responses +interface ApiUser { + id: number; + name: string; + email: string; + avatar?: string; +} + +function UserProfile({ userId }: { userId: number }) { + const { data: user, loading, error, refetch } = useApi( + `/api/users/${userId}` + ); + + if (loading) return
Loading user...
; + if (error) return
Error: {error}
; + if (!user) return
User not found
; + + return ( +
+

{user.name}

+

{user.email}

+ {user.avatar && {user.name}} + +
+ ); +} +``` + +**Form Handling with TypeScript:** +```tsx +// Type-safe form handling +interface FormData { + email: string; + password: string; + rememberMe: boolean; +} + +type FormErrors = Partial>; + +interface UseFormResult { + values: T; + errors: Partial>; + handleChange: (field: keyof T) => ( + event: React.ChangeEvent + ) => void; + handleSubmit: (onSubmit: (values: T) => void) => ( + event: React.FormEvent + ) => void; + setFieldError: (field: keyof T, error: string) => void; + clearErrors: () => void; +} + +function useForm>( + initialValues: T, + validator?: (values: T) => Partial> +): UseFormResult { + const [values, setValues] = useState(initialValues); + const [errors, setErrors] = useState>>({}); + + const handleChange = useCallback((field: keyof T) => + (event: React.ChangeEvent) => { + const { value, type, checked } = event.target; + setValues(prev => ({ + ...prev, + [field]: type === 'checkbox' ? checked : value + })); + + // Clear error when user starts typing + if (errors[field]) { + setErrors(prev => ({ ...prev, [field]: undefined })); + } + }, [errors] + ); + + const setFieldError = useCallback((field: keyof T, error: string) => { + setErrors(prev => ({ ...prev, [field]: error })); + }, []); + + const clearErrors = useCallback(() => { + setErrors({}); + }, []); + + const handleSubmit = useCallback((onSubmit: (values: T) => void) => + (event: React.FormEvent) => { + event.preventDefault(); + + const validationErrors = validator?.(values) || {}; + + if (Object.keys(validationErrors).length > 0) { + setErrors(validationErrors); + return; + } + + onSubmit(values); + }, [values, validator] + ); + + return { + values, + errors, + handleChange, + handleSubmit, + setFieldError, + clearErrors + }; +} + +// Login form implementation +function LoginForm() { + const validateForm = (values: FormData): FormErrors => { + const errors: FormErrors = {}; + + if (!values.email) { + errors.email = 'Email is required'; + } else if (!/\S+@\S+\.\S+/.test(values.email)) { + errors.email = 'Email is invalid'; + } + + if (!values.password) { + errors.password = 'Password is required'; + } else if (values.password.length < 6) { + errors.password = 'Password must be at least 6 characters'; + } + + return errors; + }; + + const { + values, + errors, + handleChange, + handleSubmit, + setFieldError + } = useForm( + { email: '', password: '', rememberMe: false }, + validateForm + ); + + const onSubmit = async (formData: FormData) => { + try { + const response = await fetch('/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(formData) + }); + + if (!response.ok) { + setFieldError('email', 'Invalid credentials'); + return; + } + + console.log('Login successful!'); + } catch (error) { + setFieldError('email', 'Network error occurred'); + } + }; + + return ( +
+
+ + + {errors.email && {errors.email}} +
+ +
+ + + {errors.password && {errors.password}} +
+ +
+ +
+ + +
+ ); +} +``` + +**Component Props with Variants:** +```tsx +// Button component with variants +type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'outline'; +type ButtonSize = 'sm' | 'md' | 'lg'; + +interface BaseButtonProps { + variant?: ButtonVariant; + size?: ButtonSize; + loading?: boolean; + disabled?: boolean; + children: React.ReactNode; +} + +type ButtonProps = BaseButtonProps & + React.ButtonHTMLAttributes; + +const Button = React.forwardRef( + ({ + variant = 'primary', + size = 'md', + loading = false, + disabled = false, + children, + className = '', + ...rest + }, ref) => { + const baseClasses = 'btn'; + const variantClasses = `btn--${variant}`; + const sizeClasses = `btn--${size}`; + const loadingClasses = loading ? 'btn--loading' : ''; + + const buttonClasses = [ + baseClasses, + variantClasses, + sizeClasses, + loadingClasses, + className + ].filter(Boolean).join(' '); + + return ( + + ); + } +); + +Button.displayName = 'Button'; + +// Usage +function App() { + return ( +
+ + + + + +
+ ); +} +``` + +
+ +--- + +## React Testing & Best Practices + +
+ +

86. How do you test React components with React Testing Library and TypeScript?

+
+ +React Testing Library with TypeScript provides excellent tools for testing React components in a way that resembles how users interact with your application. + +**Basic Component Testing:** +```tsx +// UserCard.tsx +interface User { + id: number; + name: string; + email: string; + isActive: boolean; +} + +interface UserCardProps { + user: User; + onEdit: (userId: number) => void; + onDelete: (userId: number) => void; +} + +export function UserCard({ user, onEdit, onDelete }: UserCardProps) { + return ( +
+

{user.name}

+

{user.email}

+ + {user.isActive ? 'Active' : 'Inactive'} + + + +
+ ); +} + +// UserCard.test.tsx +import { render, screen, fireEvent } from '@testing-library/react'; +import { UserCard } from './UserCard'; + +const mockUser: User = { + id: 1, + name: 'John Doe', + email: 'john@example.com', + isActive: true +}; + +describe('UserCard', () => { + const mockOnEdit = jest.fn(); + const mockOnDelete = jest.fn(); + + beforeEach(() => { + mockOnEdit.mockClear(); + mockOnDelete.mockClear(); + }); + + it('renders user information correctly', () => { + render( + + ); + + expect(screen.getByText('John Doe')).toBeInTheDocument(); + expect(screen.getByText('john@example.com')).toBeInTheDocument(); + expect(screen.getByText('Active')).toBeInTheDocument(); + }); + + it('calls onEdit when edit button is clicked', () => { + render( + + ); + + fireEvent.click(screen.getByText('Edit')); + expect(mockOnEdit).toHaveBeenCalledWith(1); + expect(mockOnEdit).toHaveBeenCalledTimes(1); + }); + + it('calls onDelete when delete button is clicked', () => { + render( + + ); + + fireEvent.click(screen.getByText('Delete')); + expect(mockOnDelete).toHaveBeenCalledWith(1); + }); + + it('displays correct status for inactive user', () => { + const inactiveUser = { ...mockUser, isActive: false }; + + render( + + ); + + expect(screen.getByText('Inactive')).toBeInTheDocument(); + expect(screen.getByText('Inactive')).toHaveClass('inactive'); + }); +}); +``` + +**Testing Custom Hooks:** +```tsx +// useCounter.ts +import { useState, useCallback } from 'react'; + +interface UseCounterResult { + count: number; + increment: () => void; + decrement: () => void; + reset: () => void; + setCount: (value: number) => void; +} + +export function useCounter(initialValue: number = 0): UseCounterResult { + const [count, setCount] = useState(initialValue); + + const increment = useCallback(() => { + setCount(prev => prev + 1); + }, []); + + const decrement = useCallback(() => { + setCount(prev => prev - 1); + }, []); + + const reset = useCallback(() => { + setCount(initialValue); + }, [initialValue]); + + return { + count, + increment, + decrement, + reset, + setCount + }; +} + +// useCounter.test.ts +import { renderHook, act } from '@testing-library/react'; +import { useCounter } from './useCounter'; + +describe('useCounter', () => { + it('initializes with default value', () => { + const { result } = renderHook(() => useCounter()); + expect(result.current.count).toBe(0); + }); + + it('initializes with custom value', () => { + const { result } = renderHook(() => useCounter(10)); + expect(result.current.count).toBe(10); + }); + + it('increments count', () => { + const { result } = renderHook(() => useCounter(5)); + + act(() => { + result.current.increment(); + }); + + expect(result.current.count).toBe(6); + }); + + it('decrements count', () => { + const { result } = renderHook(() => useCounter(5)); + + act(() => { + result.current.decrement(); + }); + + expect(result.current.count).toBe(4); + }); + + it('resets to initial value', () => { + const { result } = renderHook(() => useCounter(10)); + + act(() => { + result.current.increment(); + result.current.increment(); + }); + + expect(result.current.count).toBe(12); + + act(() => { + result.current.reset(); + }); + + expect(result.current.count).toBe(10); + }); + + it('sets count to specific value', () => { + const { result } = renderHook(() => useCounter()); + + act(() => { + result.current.setCount(25); + }); + + expect(result.current.count).toBe(25); + }); +}); +``` + +**Testing Components with Context:** +```tsx +// AuthContext.tsx +interface User { + id: string; + name: string; + role: 'admin' | 'user'; +} + +interface AuthContextType { + user: User | null; + login: (user: User) => void; + logout: () => void; + isAdmin: boolean; +} + +const AuthContext = createContext(null); + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [user, setUser] = useState(null); + + const login = useCallback((user: User) => { + setUser(user); + }, []); + + const logout = useCallback(() => { + setUser(null); + }, []); + + const isAdmin = user?.role === 'admin'; + + return ( + + {children} + + ); +} + +export function useAuth() { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within AuthProvider'); + } + return context; +} + +// AdminPanel.tsx +export function AdminPanel() { + const { user, isAdmin, logout } = useAuth(); + + if (!isAdmin) { + return
Access denied. Admin rights required.
; + } + + return ( +
+

Admin Panel

+

Welcome, {user?.name}

+ +
+ ); +} + +// AdminPanel.test.tsx +import { render, screen, fireEvent } from '@testing-library/react'; +import { AuthProvider } from './AuthContext'; +import { AdminPanel } from './AdminPanel'; + +const renderWithAuth = (ui: React.ReactElement, { user = null } = {}) => { + function Wrapper({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); + } + + const result = render(ui, { wrapper: Wrapper }); + + // If user is provided, log them in + if (user) { + const authContext = result.container.querySelector('[data-testid="auth-provider"]'); + // In real implementation, you'd expose the login function through a test helper + } + + return result; +}; + +// Custom render helper with authenticated user +const renderWithAuthenticatedUser = ( + ui: React.ReactElement, + user: User +) => { + function AuthWrapper({ children }: { children: React.ReactNode }) { + return ( + +
+ {children} +
+
+ ); + } + + const result = render(ui, { wrapper: AuthWrapper }); + + // You would typically expose a way to set the user in tests + // This is a simplified example + return result; +}; + +describe('AdminPanel', () => { + const adminUser: User = { + id: '1', + name: 'Admin User', + role: 'admin' + }; + + const regularUser: User = { + id: '2', + name: 'Regular User', + role: 'user' + }; + + it('denies access for non-admin users', () => { + // This test would need proper setup with the context + render( + + + + ); + + expect(screen.getByText('Access denied. Admin rights required.')).toBeInTheDocument(); + }); + + it('shows admin panel for admin users', () => { + // You would set up the authenticated admin user here + // This is a simplified test structure + }); +}); +``` + +**Testing Async Components:** +```tsx +// UserList.tsx +interface User { + id: number; + name: string; + email: string; +} + +export function UserList() { + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchUsers = async () => { + try { + const response = await fetch('/api/users'); + if (!response.ok) { + throw new Error('Failed to fetch users'); + } + const userData = await response.json(); + setUsers(userData); + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error'); + } finally { + setLoading(false); + } + }; + + fetchUsers(); + }, []); + + if (loading) return
Loading users...
; + if (error) return
Error: {error}
; + + return ( +
+

Users

+ {users.map(user => ( +
+

{user.name}

+

{user.email}

+
+ ))} +
+ ); +} + +// UserList.test.tsx +import { render, screen, waitFor } from '@testing-library/react'; +import { UserList } from './UserList'; + +// Mock fetch +global.fetch = jest.fn(); +const mockFetch = fetch as jest.MockedFunction; + +const mockUsers: User[] = [ + { id: 1, name: 'John Doe', email: 'john@example.com' }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com' } +]; + +describe('UserList', () => { + beforeEach(() => { + mockFetch.mockClear(); + }); + + it('displays loading state initially', () => { + mockFetch.mockReturnValue( + Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockUsers) + } as Response) + ); + + render(); + expect(screen.getByText('Loading users...')).toBeInTheDocument(); + }); + + it('displays users after successful fetch', async () => { + mockFetch.mockResolvedValue({ + ok: true, + json: () => Promise.resolve(mockUsers) + } as Response); + + render(); + + await waitFor(() => { + expect(screen.getByText('Users')).toBeInTheDocument(); + }); + + expect(screen.getByText('John Doe')).toBeInTheDocument(); + expect(screen.getByText('jane@example.com')).toBeInTheDocument(); + }); + + it('displays error message on fetch failure', async () => { + mockFetch.mockRejectedValue(new Error('Network error')); + + render(); + + await waitFor(() => { + expect(screen.getByText(/Error: Network error/)).toBeInTheDocument(); + }); + }); + + it('handles HTTP error responses', async () => { + mockFetch.mockResolvedValue({ + ok: false, + status: 404 + } as Response); + + render(); + + await waitFor(() => { + expect(screen.getByText(/Error: Failed to fetch users/)).toBeInTheDocument(); + }); + }); +}); +``` + +**Test Setup Configuration:** +```typescript +// setupTests.ts +import '@testing-library/jest-dom'; +import { configure } from '@testing-library/react'; + +// Configure testing library +configure({ testIdAttribute: 'data-testid' }); + +// Mock IntersectionObserver +global.IntersectionObserver = class IntersectionObserver { + constructor() {} + disconnect() {} + observe() {} + unobserve() {} +}; + +// Mock ResizeObserver +global.ResizeObserver = class ResizeObserver { + constructor(cb: any) {} + observe() {} + unobserve() {} + disconnect() {} +}; +``` + +
+ +--- + +## React Security & Accessibility + +
+ +

87. What are the common security vulnerabilities in React applications and how to prevent them?

+
+ +React applications can be vulnerable to several security threats. Here are the most common ones and how to prevent them: + +**1. Cross-Site Scripting (XSS):** +```tsx +// โŒ Dangerous - allows XSS attacks +interface UnsafeComponentProps { + userContent: string; +} + +function UnsafeComponent({ userContent }: UnsafeComponentProps) { + // This directly injects HTML and can execute malicious scripts + return
; +} + +// โœ… Safe approaches +import DOMPurify from 'dompurify'; + +function SafeComponent({ userContent }: UnsafeComponentProps) { + // Approach 1: Use React's built-in XSS protection + return
{userContent}
; // React automatically escapes content + + // Approach 2: Sanitize HTML content + const sanitizedContent = DOMPurify.sanitize(userContent); + return
; +} + +// Safe handling of user input +function CommentComponent({ comment }: { comment: string }) { + // React automatically escapes this content + return ( +
+

{comment}

{/* Safe - React escapes special characters */} +
+ ); +} +``` + +**2. Dependency Vulnerabilities:** +```bash +# Regular security audits +npm audit +npm audit fix + +# Use tools like Snyk +npx snyk test +npx snyk monitor + +# Keep dependencies updated +npm update +npm outdated +``` + +**3. Environment Variables Security:** +```typescript +// โŒ Don't expose sensitive data in client-side code +const API_KEY = process.env.REACT_APP_SECRET_API_KEY; // Exposed to client! + +// โœ… Safe approach +// Only expose non-sensitive public variables +const PUBLIC_API_URL = process.env.REACT_APP_PUBLIC_API_URL; + +// Keep sensitive data on the server +// server-side API call +async function getSecureData() { + const response = await fetch('/api/secure-endpoint', { + headers: { + 'Authorization': `Bearer ${await getAuthToken()}` // Server-side token + } + }); + return response.json(); +} +``` + +**4. Authentication & Authorization:** +```tsx +// Secure authentication implementation +interface AuthToken { + token: string; + expiresAt: number; + refreshToken: string; +} + +class AuthService { + private static readonly TOKEN_KEY = 'auth_token'; + private static readonly REFRESH_KEY = 'refresh_token'; + + static setTokens(tokens: AuthToken): void { + // Store tokens securely (consider httpOnly cookies for production) + localStorage.setItem(this.TOKEN_KEY, tokens.token); + localStorage.setItem(this.REFRESH_KEY, tokens.refreshToken); + } + + static getToken(): string | null { + const token = localStorage.getItem(this.TOKEN_KEY); + if (token && this.isTokenValid(token)) { + return token; + } + return null; + } + + static isTokenValid(token: string): boolean { + try { + const payload = JSON.parse(atob(token.split('.')[1])); + return payload.exp * 1000 > Date.now(); + } catch { + return false; + } + } + + static async refreshToken(): Promise { + const refreshToken = localStorage.getItem(this.REFRESH_KEY); + if (!refreshToken) return null; + + try { + const response = await fetch('/api/auth/refresh', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ refreshToken }) + }); + + if (response.ok) { + const newTokens = await response.json(); + this.setTokens(newTokens); + return newTokens.token; + } + } catch (error) { + console.error('Token refresh failed:', error); + } + + return null; + } + + static clearTokens(): void { + localStorage.removeItem(this.TOKEN_KEY); + localStorage.removeItem(this.REFRESH_KEY); + } +} + +// Protected Route Component +function ProtectedRoute({ + children, + requiredRole +}: { + children: React.ReactNode; + requiredRole?: string +}) { + const { user, isAuthenticated } = useAuth(); + + if (!isAuthenticated) { + return ; + } + + if (requiredRole && user?.role !== requiredRole) { + return ; + } + + return <>{children}; +} +``` + +**5. CSRF Protection:** +```tsx +// CSRF token handling +function CSRFProtectedForm() { + const [csrfToken, setCsrfToken] = useState(''); + + useEffect(() => { + // Get CSRF token from meta tag or API + const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'); + setCsrfToken(token || ''); + }, []); + + const handleSubmit = async (formData: any) => { + await fetch('/api/protected-endpoint', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': csrfToken, // Include CSRF token + 'Authorization': `Bearer ${AuthService.getToken()}` + }, + body: JSON.stringify(formData) + }); + }; + + return ( +
+ {/* Form content */} + +
+ ); +} +``` + +**6. Content Security Policy (CSP):** +```html + + +``` + +**7. Input Validation:** +```tsx +// Comprehensive input validation +interface FormValidation { + email: (value: string) => string | null; + password: (value: string) => string | null; + url: (value: string) => string | null; +} + +const validation: FormValidation = { + email: (value: string) => { + if (!value) return 'Email is required'; + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { + return 'Please enter a valid email address'; + } + if (value.length > 254) return 'Email is too long'; + return null; + }, + + password: (value: string) => { + if (!value) return 'Password is required'; + if (value.length < 8) return 'Password must be at least 8 characters'; + if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) { + return 'Password must contain uppercase, lowercase, and number'; + } + return null; + }, + + url: (value: string) => { + if (!value) return null; // Optional field + try { + const url = new URL(value); + if (!['http:', 'https:'].includes(url.protocol)) { + return 'URL must use HTTP or HTTPS protocol'; + } + return null; + } catch { + return 'Please enter a valid URL'; + } + } +}; + +// Secure form component +function SecureForm() { + const [formData, setFormData] = useState({ + email: '', + password: '', + website: '' + }); + const [errors, setErrors] = useState>({}); + + const validateField = (name: string, value: string) => { + const validator = validation[name as keyof FormValidation]; + return validator ? validator(value) : null; + }; + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + + // Sanitize input + const sanitizedValue = value.trim().slice(0, 1000); // Limit length + + setFormData(prev => ({ ...prev, [name]: sanitizedValue })); + + // Real-time validation + const error = validateField(name, sanitizedValue); + setErrors(prev => ({ ...prev, [name]: error || '' })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Validate all fields + const newErrors: Record = {}; + Object.entries(formData).forEach(([key, value]) => { + const error = validateField(key, value); + if (error) newErrors[key] = error; + }); + + if (Object.keys(newErrors).length > 0) { + setErrors(newErrors); + return; + } + + // Submit data securely + await submitForm(formData); + }; + + return ( +
+
+ + {errors.email && } +
+ +
+ + {errors.password &&
{errors.password}
} +
+ + +
+ ); +} +``` + +
+ +
+ +

88. How do you implement accessibility (a11y) in React applications?

+
+ +Accessibility is crucial for making React applications usable by everyone. Here are key techniques and patterns: + +**1. Semantic HTML and ARIA:** +```tsx +// Good semantic structure +interface NavigationProps { + items: Array<{ href: string; label: string; current?: boolean }>; +} + +function AccessibleNavigation({ items }: NavigationProps) { + return ( + + ); +} + +// Modal with proper ARIA +interface ModalProps { + isOpen: boolean; + onClose: () => void; + title: string; + children: React.ReactNode; +} + +function AccessibleModal({ isOpen, onClose, title, children }: ModalProps) { + const modalRef = useRef(null); + const previousFocusRef = useRef(null); + + useEffect(() => { + if (isOpen) { + // Store current focus + previousFocusRef.current = document.activeElement as HTMLElement; + + // Focus modal + modalRef.current?.focus(); + + // Trap focus within modal + const trapFocus = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + onClose(); + } + }; + + document.addEventListener('keydown', trapFocus); + return () => document.removeEventListener('keydown', trapFocus); + } else { + // Return focus to previous element + previousFocusRef.current?.focus(); + } + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
e.target === e.currentTarget && onClose()} + > +
+
+ + +
+
+ {children} +
+
+
+ ); +} +``` + +**2. Form Accessibility:** +```tsx +// Comprehensive accessible form +interface FormField { + id: string; + label: string; + type: 'text' | 'email' | 'password' | 'tel'; + required?: boolean; + placeholder?: string; + autoComplete?: string; +} + +interface AccessibleFormProps { + fields: FormField[]; + onSubmit: (data: Record) => void; +} + +function AccessibleForm({ fields, onSubmit }: AccessibleFormProps) { + const [formData, setFormData] = useState>({}); + const [errors, setErrors] = useState>({}); + const [isSubmitting, setIsSubmitting] = useState(false); + + const firstErrorRef = useRef(null); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + + // Validate form + const newErrors: Record = {}; + fields.forEach(field => { + if (field.required && !formData[field.id]) { + newErrors[field.id] = `${field.label} is required`; + } + }); + + if (Object.keys(newErrors).length > 0) { + setErrors(newErrors); + // Focus first error field + setTimeout(() => firstErrorRef.current?.focus(), 0); + setIsSubmitting(false); + return; + } + + try { + await onSubmit(formData); + } catch (error) { + setErrors({ general: 'An error occurred while submitting the form' }); + } finally { + setIsSubmitting(false); + } + }; + + const handleChange = (fieldId: string, value: string) => { + setFormData(prev => ({ ...prev, [fieldId]: value })); + // Clear error when user starts typing + if (errors[fieldId]) { + setErrors(prev => ({ ...prev, [fieldId]: '' })); + } + }; + + return ( +
+
+ Registration Form + + {errors.general && ( +
+ {errors.general} +
+ )} + + {fields.map((field, index) => ( +
+ + + 0 ? firstErrorRef : null} + id={field.id} + type={field.type} + value={formData[field.id] || ''} + onChange={(e) => handleChange(field.id, e.target.value)} + placeholder={field.placeholder} + autoComplete={field.autoComplete} + required={field.required} + aria-invalid={!!errors[field.id]} + aria-describedby={errors[field.id] ? `${field.id}-error` : undefined} + className={errors[field.id] ? 'error' : ''} + /> + + {errors[field.id] && ( + + )} +
+ ))} + + + + {isSubmitting && ( +
+ Form is being submitted +
+ )} +
+
+ ); +} +``` + +**3. Focus Management:** +```tsx +// Custom hook for focus management +function useFocusTrap(isActive: boolean) { + const containerRef = useRef(null); + + useEffect(() => { + if (!isActive) return; + + const container = containerRef.current; + if (!container) return; + + const focusableElements = container.querySelectorAll( + 'a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select' + ) as NodeListOf; + + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + const handleTabKey = (e: KeyboardEvent) => { + if (e.key === 'Tab') { + if (e.shiftKey) { + if (document.activeElement === firstElement) { + lastElement.focus(); + e.preventDefault(); + } + } else { + if (document.activeElement === lastElement) { + firstElement.focus(); + e.preventDefault(); + } + } + } + }; + + document.addEventListener('keydown', handleTabKey); + firstElement?.focus(); + + return () => { + document.removeEventListener('keydown', handleTabKey); + }; + }, [isActive]); + + return containerRef; +} + +// Skip links for keyboard navigation +function SkipLinks() { + return ( + + ); +} +``` + +**4. Screen Reader Support:** +```tsx +// Live regions for dynamic content +function useAnnouncement() { + const [announcement, setAnnouncement] = useState(''); + + const announce = useCallback((message: string) => { + setAnnouncement(message); + // Clear after announcement + setTimeout(() => setAnnouncement(''), 1000); + }, []); + + const AnnouncementRegion = useCallback(() => ( +
+ {announcement} +
+ ), [announcement]); + + return { announce, AnnouncementRegion }; +} + +// Data table with proper headers +interface TableData { + id: string; + name: string; + email: string; + status: 'active' | 'inactive'; +} + +interface AccessibleTableProps { + data: TableData[]; + caption: string; +} + +function AccessibleTable({ data, caption }: AccessibleTableProps) { + const { announce, AnnouncementRegion } = useAnnouncement(); + + const handleStatusToggle = (userId: string, currentStatus: string) => { + const newStatus = currentStatus === 'active' ? 'inactive' : 'active'; + // Update logic here + announce(`User status changed to ${newStatus}`); + }; + + return ( +
+ + + + + + + + + + + + {data.map((user) => ( + + + + + + + ))} + +
{caption}
NameEmailStatusActions
{user.name}{user.email} + + {user.status} + + + +
+ Current status: {user.status}. Click to change to{' '} + {user.status === 'active' ? 'inactive' : 'active'} +
+
+ +
+ ); +} +``` + +**5. CSS for Accessibility:** +```css +/* Screen reader only content */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Skip links */ +.skip-link { + position: absolute; + top: -40px; + left: 6px; + background: #000; + color: #fff; + padding: 8px; + text-decoration: none; + border-radius: 0 0 4px 4px; + z-index: 1000; +} + +.skip-link:focus { + top: 0; +} + +/* Focus styles */ +*:focus { + outline: 2px solid #005fcc; + outline-offset: 2px; +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .button { + border: 2px solid; + } +} + +/* Reduced motion support */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} +``` + +**6. Testing Accessibility:** +```tsx +// Accessibility testing utilities +import { axe, toHaveNoViolations } from 'jest-axe'; + +expect.extend(toHaveNoViolations); + +describe('AccessibleForm', () => { + it('should have no accessibility violations', async () => { + const { container } = render( + + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should have proper ARIA labels', () => { + render( + + ); + + expect(screen.getByLabelText('Email *')).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /email/i })).toBeInTheDocument(); + }); +}); +``` + +
+ +--- + +## Advanced React Patterns + +
+ +

89. Explain Compound Components pattern and provide a TypeScript example.

+
+ +Compound Components is a pattern where components work together to form a complete UI. The parent component manages the state and passes it down to child components through context. + +```tsx +// Accordion implementation using Compound Components +interface AccordionContextType { + activeIndex: number | null; + setActiveIndex: (index: number | null) => void; +} + +const AccordionContext = createContext(null); + +function useAccordion() { + const context = useContext(AccordionContext); + if (!context) { + throw new Error('Accordion compound components must be used within Accordion'); + } + return context; +} + +// Main Accordion component +interface AccordionProps { + children: React.ReactNode; + defaultActiveIndex?: number | null; + allowMultiple?: boolean; +} + +function Accordion({ children, defaultActiveIndex = null, allowMultiple = false }: AccordionProps) { + const [activeIndex, setActiveIndex] = useState(defaultActiveIndex); + + const handleSetActiveIndex = (index: number | null) => { + if (allowMultiple) { + // Implementation for multiple panels (would need array state) + return; + } + setActiveIndex(activeIndex === index ? null : index); + }; + + return ( + +
{children}
+
+ ); +} + +// AccordionItem component +interface AccordionItemProps { + children: React.ReactNode; + index: number; +} + +function AccordionItem({ children, index }: AccordionItemProps) { + const { activeIndex } = useAccordion(); + const isActive = activeIndex === index; + + return ( +
+ {children} +
+ ); +} + +// AccordionHeader component +interface AccordionHeaderProps { + children: React.ReactNode; +} + +function AccordionHeader({ children }: AccordionHeaderProps) { + const { setActiveIndex } = useAccordion(); + const itemElement = useRef(); + + useEffect(() => { + // Find parent AccordionItem to get index + let element = itemElement.current?.parentElement; + while (element && !element.hasAttribute('data-index')) { + element = element.parentElement; + } + itemElement.current = element as HTMLElement; + }, []); + + const handleClick = () => { + const index = itemElement.current?.getAttribute('data-index'); + if (index !== null && index !== undefined) { + setActiveIndex(parseInt(index)); + } + }; + + return ( + + ); +} + +// AccordionPanel component +interface AccordionPanelProps { + children: React.ReactNode; +} + +function AccordionPanel({ children }: AccordionPanelProps) { + const { activeIndex } = useAccordion(); + const itemElement = useRef(); + const [isActive, setIsActive] = useState(false); + + useEffect(() => { + let element = itemElement.current?.parentElement; + while (element && !element.hasAttribute('data-index')) { + element = element.parentElement; + } + + if (element) { + const index = parseInt(element.getAttribute('data-index') || '0'); + setIsActive(activeIndex === index); + } + }, [activeIndex]); + + return ( +
+ {children} +
+ ); +} + +// Compound component assignment +Accordion.Item = AccordionItem; +Accordion.Header = AccordionHeader; +Accordion.Panel = AccordionPanel; + +// Usage +function App() { + return ( + + + What is React? + + React is a JavaScript library for building user interfaces. + + + + + What is TypeScript? + + TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. + + + + + What are React Hooks? + + Hooks are functions that let you use state and other React features without writing a class. + + + + ); +} +``` + +**Alternative implementation with better TypeScript support:** + +```tsx +// More type-safe compound component approach +interface TabsContextType { + activeTab: string; + setActiveTab: (tabId: string) => void; +} + +const TabsContext = createContext(null); + +interface TabsProviderProps { + children: React.ReactNode; + defaultTab?: string; +} + +function TabsProvider({ children, defaultTab = '' }: TabsProviderProps) { + const [activeTab, setActiveTab] = useState(defaultTab); + + return ( + + {children} + + ); +} + +// Tab components +interface TabsProps { + children: React.ReactNode; + defaultTab?: string; +} + +function Tabs({ children, defaultTab }: TabsProps) { + return ( + +
{children}
+
+ ); +} + +interface TabListProps { + children: React.ReactNode; +} + +function TabList({ children }: TabListProps) { + return
{children}
; +} + +interface TabProps { + tabId: string; + children: React.ReactNode; + disabled?: boolean; +} + +function Tab({ tabId, children, disabled = false }: TabProps) { + const context = useContext(TabsContext); + if (!context) throw new Error('Tab must be used within Tabs'); + + const { activeTab, setActiveTab } = context; + const isActive = activeTab === tabId; + + return ( + + ); +} + +interface TabPanelsProps { + children: React.ReactNode; +} + +function TabPanels({ children }: TabPanelsProps) { + return
{children}
; +} + +interface TabPanelProps { + tabId: string; + children: React.ReactNode; +} + +function TabPanel({ tabId, children }: TabPanelProps) { + const context = useContext(TabsContext); + if (!context) throw new Error('TabPanel must be used within Tabs'); + + const { activeTab } = context; + const isActive = activeTab === tabId; + + return ( + + ); +} + +// Export compound component +const TabsCompound = Object.assign(Tabs, { + List: TabList, + Tab, + Panels: TabPanels, + Panel: TabPanel, +}); + +// Usage with proper TypeScript support +function TabExample() { + return ( + + + Profile + Settings + Billing + + + + +

Profile Content

+

Manage your profile information here.

+
+ + +

Settings Content

+

Adjust your application settings.

+
+ + +

Billing Content

+

Manage your billing information.

+
+
+
+ ); +} + +export default TabsCompound; +``` + +**Benefits of Compound Components:** +- **Flexible API**: Users can compose components however they need +- **Separation of Concerns**: Each component has a single responsibility +- **Implicit State Sharing**: Context eliminates prop drilling +- **Type Safety**: TypeScript ensures correct usage + +
+ +
+ +

90. What is the Render Props pattern and how does it compare to custom hooks?

+
+ +Render Props is a pattern where a component accepts a function as a prop that returns React elements. This pattern enables component logic sharing and inversion of control. + +**Render Props Implementation:** + +```tsx +// Mouse position tracker using render props +interface MousePosition { + x: number; + y: number; +} + +interface MouseTrackerProps { + children: (mouse: MousePosition) => React.ReactNode; + // Alternative: render prop + render?: (mouse: MousePosition) => React.ReactNode; +} + +class MouseTracker extends React.Component { + state: MousePosition = { x: 0, y: 0 }; + + handleMouseMove = (event: MouseEvent) => { + this.setState({ + x: event.clientX, + y: event.clientY, + }); + }; + + componentDidMount() { + window.addEventListener('mousemove', this.handleMouseMove); + } + + componentWillUnmount() { + window.removeEventListener('mousemove', this.handleMouseMove); + } + + render() { + const { children, render } = this.props; + return ( +
+ {/* Use children function or render prop */} + {render ? render(this.state) : children(this.state)} +
+ ); + } +} + +// Usage with children function +function App() { + return ( + + {({ x, y }) => ( +
+

Mouse Position

+

X: {x}, Y: {y}

+
+
+ )} + + ); +} + +// More complex render props example - Data fetcher +interface DataFetcherState { + data: T | null; + loading: boolean; + error: Error | null; +} + +interface DataFetcherProps { + url: string; + children: (state: DataFetcherState) => React.ReactNode; +} + +class DataFetcher extends React.Component< + DataFetcherProps, + DataFetcherState +> { + state: DataFetcherState = { + data: null, + loading: true, + error: null, + }; + + async componentDidMount() { + await this.fetchData(); + } + + async componentDidUpdate(prevProps: DataFetcherProps) { + if (prevProps.url !== this.props.url) { + await this.fetchData(); + } + } + + fetchData = async () => { + this.setState({ loading: true, error: null }); + + try { + const response = await fetch(this.props.url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + this.setState({ data, loading: false }); + } catch (error) { + this.setState({ + error: error instanceof Error ? error : new Error('Unknown error'), + loading: false + }); + } + }; + + render() { + return this.props.children(this.state); + } +} + +// Usage +interface User { + id: number; + name: string; + email: string; +} + +function UserProfile({ userId }: { userId: number }) { + return ( + url={`/api/users/${userId}`}> + {({ data: user, loading, error }) => { + if (loading) return
Loading user...
; + if (error) return
Error: {error.message}
; + if (!user) return
User not found
; + + return ( +
+

{user.name}

+

{user.email}

+
+ ); + }} + + ); +} +``` + +**Converting to Custom Hooks (Modern Approach):** + +```tsx +// Custom hook version of mouse tracker +function useMousePosition(): MousePosition { + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + + useEffect(() => { + const handleMouseMove = (event: MouseEvent) => { + setMousePosition({ + x: event.clientX, + y: event.clientY, + }); + }; + + window.addEventListener('mousemove', handleMouseMove); + return () => window.removeEventListener('mousemove', handleMouseMove); + }, []); + + return mousePosition; +} + +// Custom hook version of data fetcher +function useApi(url: string): DataFetcherState & { refetch: () => void } { + const [state, setState] = useState>({ + data: null, + loading: true, + error: null, + }); + + const fetchData = useCallback(async () => { + setState(prev => ({ ...prev, loading: true, error: null })); + + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + setState({ data, loading: false, error: null }); + } catch (error) { + setState({ + data: null, + loading: false, + error: error instanceof Error ? error : new Error('Unknown error') + }); + } + }, [url]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { ...state, refetch: fetchData }; +} + +// Usage with hooks (much cleaner!) +function App() { + const mousePosition = useMousePosition(); + + return ( +
+

Mouse Position

+

X: {mousePosition.x}, Y: {mousePosition.y}

+
+
+ ); +} + +function UserProfile({ userId }: { userId: number }) { + const { data: user, loading, error, refetch } = useApi(`/api/users/${userId}`); + + if (loading) return
Loading user...
; + if (error) return ( +
+ Error: {error.message} + +
+ ); + if (!user) return
User not found
; + + return ( +
+

{user.name}

+

{user.email}

+ +
+ ); +} +``` + +**Complex Render Props Example - Form Validation:** + +```tsx +// Advanced render props for form validation +interface ValidationRules { + [K in keyof T]?: Array<(value: T[K]) => string | null>; +} + +interface FormRenderProps { + values: T; + errors: Partial>; + touched: Partial>; + handleChange: (field: keyof T) => (value: T[keyof T]) => void; + handleBlur: (field: keyof T) => () => void; + handleSubmit: (onSubmit: (values: T) => void) => (e: React.FormEvent) => void; + isValid: boolean; + isSubmitting: boolean; +} + +interface FormProps { + initialValues: T; + validationRules?: ValidationRules; + children: (props: FormRenderProps) => React.ReactNode; +} + +function Form>({ + initialValues, + validationRules = {}, + children +}: FormProps) { + const [values, setValues] = useState(initialValues); + const [errors, setErrors] = useState>>({}); + const [touched, setTouched] = useState>>({}); + const [isSubmitting, setIsSubmitting] = useState(false); + + const validateField = useCallback((field: keyof T, value: T[keyof T]): string | null => { + const rules = validationRules[field]; + if (!rules) return null; + + for (const rule of rules) { + const error = rule(value); + if (error) return error; + } + return null; + }, [validationRules]); + + const handleChange = useCallback((field: keyof T) => (value: T[keyof T]) => { + setValues(prev => ({ ...prev, [field]: value })); + + if (touched[field]) { + const error = validateField(field, value); + setErrors(prev => ({ ...prev, [field]: error })); + } + }, [touched, validateField]); + + const handleBlur = useCallback((field: keyof T) => () => { + setTouched(prev => ({ ...prev, [field]: true })); + const error = validateField(field, values[field]); + setErrors(prev => ({ ...prev, [field]: error })); + }, [values, validateField]); + + const handleSubmit = useCallback((onSubmit: (values: T) => void) => + async (e: React.FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + + // Validate all fields + const newErrors: Partial> = {}; + Object.keys(values).forEach(key => { + const field = key as keyof T; + const error = validateField(field, values[field]); + if (error) newErrors[field] = error; + }); + + setErrors(newErrors); + setTouched( + Object.keys(values).reduce((acc, key) => ({ + ...acc, + [key]: true + }), {}) + ); + + if (Object.keys(newErrors).length === 0) { + try { + await onSubmit(values); + } catch (error) { + console.error('Form submission error:', error); + } + } + + setIsSubmitting(false); + }, [values, validateField] + ); + + const isValid = useMemo(() => { + return Object.keys(values).every(key => { + const field = key as keyof T; + return !validateField(field, values[field]); + }); + }, [values, validateField]); + + return ( + <> + {children({ + values, + errors, + touched, + handleChange, + handleBlur, + handleSubmit, + isValid, + isSubmitting + })} + + ); +} + +// Usage +interface LoginForm { + email: string; + password: string; +} + +const validationRules: ValidationRules = { + email: [ + (value) => value ? null : 'Email is required', + (value) => /\S+@\S+\.\S+/.test(value) ? null : 'Email is invalid' + ], + password: [ + (value) => value ? null : 'Password is required', + (value) => value.length >= 6 ? null : 'Password must be at least 6 characters' + ] +}; + +function LoginPage() { + const handleLogin = async (values: LoginForm) => { + console.log('Logging in with:', values); + // Simulate API call + await new Promise(resolve => setTimeout(resolve, 1000)); + }; + + return ( +
+ {({ values, errors, touched, handleChange, handleBlur, handleSubmit, isValid, isSubmitting }) => ( + +
+ handleChange('email')(e.target.value)} + onBlur={handleBlur('email')} + /> + {touched.email && errors.email && {errors.email}} +
+ +
+ handleChange('password')(e.target.value)} + onBlur={handleBlur('password')} + /> + {touched.password && errors.password && {errors.password}} +
+ + +
+ )} + + ); +} +``` + +**Render Props vs Hooks Comparison:** + +| Feature | Render Props | Custom Hooks | +|---------|--------------|--------------| +| **Syntax** | More verbose | Cleaner, simpler | +| **Reusability** | Good | Excellent | +| **Composition** | Nested functions | Linear composition | +| **TypeScript Support** | Complex generics | Better inference | +| **Performance** | Can cause extra renders | More optimized | +| **Learning Curve** | Steeper | Easier | +| **Modern React** | Legacy pattern | Recommended approach | + +**When to Use Each:** +- **Render Props**: When you need very flexible component composition or working with class components +- **Custom Hooks**: For most modern React applications, simpler logic sharing, and better performance + +
From 388330f2a6780b959a078aa729673b9813736fb1 Mon Sep 17 00:00:00 2001 From: Pankaj singh <114842051+PankajSingh34@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:40:05 +0530 Subject: [PATCH 2/2] Improve code consistency and formatting in README Standardized code formatting, updated import statements to use double quotes, improved indentation, and enhanced code examples for clarity. These changes make the README easier to read and maintain, and ensure consistency across all code snippets and documentation sections. --- README.md | 1073 +++++++++++++++++++++++++++++------------------------ 1 file changed, 589 insertions(+), 484 deletions(-) diff --git a/README.md b/README.md index ca59eed..9f266f9 100644 --- a/README.md +++ b/README.md @@ -29,26 +29,32 @@ Here you'll find **90+ comprehensive React.js interview questions and answers** ## ๐Ÿ“š Table of Contents 1. **[React Fundamentals](#react-js-interview-questions)** (Questions 1-52) + - Basic concepts, components, hooks, lifecycle methods - State management, props, JSX, Virtual DOM 2. **[React 19 & Modern Features](#react-19-features--modern-react)** (Questions 79-83) + - New `use()` hook, React Compiler, Server Components - App Router, Suspense, and concurrent features 3. **[Performance & TypeScript](#performance-optimization--typescript)** (Questions 84-85) + - Advanced memoization, useMemo, useCallback - TypeScript patterns, generic components, type safety 4. **[Testing & Quality](#react-testing--best-practices)** (Question 86) + - React Testing Library, custom hook testing - Component testing, mocking, accessibility testing 5. **[Security & Accessibility](#react-security--accessibility)** (Questions 87-88) + - XSS prevention, authentication, CSRF protection - Screen readers, ARIA, keyboard navigation 6. **[Advanced Patterns](#advanced-react-patterns)** (Questions 89-90) + - Compound components, render props - Custom hooks vs render props comparison @@ -2642,22 +2648,25 @@ React 19 introduces several powerful features that enhance developer experience - **`useActionState()`**: For managing server actions state **React Compiler (Automatic Memoization):** + - Automatically optimizes your components without manual memoization - Eliminates the need for `useMemo`, `useCallback`, and `React.memo` in many cases **Server Components Enhancements:** + - Improved Server Components with better hydration - Enhanced streaming capabilities - Better integration with frameworks like Next.js **Actions and Forms:** + - Native support for Server Actions - Improved form handling with automatic pending states - Built-in error boundaries for async operations ```tsx // React 19 - use() hook example -import { use } from 'react'; +import { use } from "react"; interface User { id: number; @@ -2666,19 +2675,19 @@ interface User { function UserProfile({ userPromise }: { userPromise: Promise }) { const user = use(userPromise); - + return
Welcome, {user.name}!
; } // React 19 - useFormStatus() example -import { useFormStatus } from 'react-dom'; +import { useFormStatus } from "react-dom"; function SubmitButton() { const { pending } = useFormStatus(); - + return ( ); } @@ -2694,12 +2703,13 @@ function SubmitButton() { The `use()` hook is a new primitive in React 19 that can consume promises and context. Unlike other hooks, `use()` can be called conditionally. **Key Features:** + - Can consume Promises and Context - Can be called conditionally (unlike other hooks) - Suspends the component while waiting for Promise resolution ```tsx -import { use, Suspense } from 'react'; +import { use, Suspense } from "react"; // TypeScript interfaces interface User { @@ -2711,7 +2721,7 @@ interface User { // Consuming a Promise with use() function UserData({ userPromise }: { userPromise: Promise }) { const user = use(userPromise); - + return (

{user.name}

@@ -2721,25 +2731,28 @@ function UserData({ userPromise }: { userPromise: Promise }) { } // Consuming Context with use() -import { createContext } from 'react'; +import { createContext } from "react"; -const ThemeContext = createContext<'light' | 'dark'>('light'); +const ThemeContext = createContext<"light" | "dark">("light"); function ThemeDisplay() { const theme = use(ThemeContext); - + return
Current theme: {theme}
; } // Usage with conditional logic -function ConditionalData({ shouldFetch, dataPromise }: { +function ConditionalData({ + shouldFetch, + dataPromise, +}: { shouldFetch: boolean; dataPromise: Promise; }) { if (!shouldFetch) { return
Not fetching data
; } - + // This is allowed with use() but not with other hooks const data = use(dataPromise); return
{data.content}
; @@ -2747,8 +2760,8 @@ function ConditionalData({ shouldFetch, dataPromise }: { // App component function App() { - const userPromise = fetch('/api/user').then(res => res.json()); - + const userPromise = fetch("/api/user").then((res) => res.json()); + return ( Loading...
}> @@ -2767,6 +2780,7 @@ function App() { The React Compiler is an experimental feature in React 19 that automatically optimizes React components by adding memoization where beneficial, eliminating the need for manual optimization with `useMemo`, `useCallback`, and `React.memo`. **Key Benefits:** + - **Automatic Optimization**: No manual memoization needed - **Better Performance**: Prevents unnecessary re-renders automatically - **Developer Experience**: Focus on logic, not performance optimization @@ -2774,7 +2788,7 @@ The React Compiler is an experimental feature in React 19 that automatically opt ```tsx // Before React Compiler (Manual optimization) -import { useMemo, useCallback, memo } from 'react'; +import { useMemo, useCallback, memo } from "react"; interface User { id: number; @@ -2788,17 +2802,21 @@ interface TodoListProps { const TodoList = memo(({ users, onUserSelect }: TodoListProps) => { const expensiveComputation = useMemo(() => { - return users.filter(user => user.name.length > 5) - .map(user => ({ ...user, displayName: user.name.toUpperCase() })); + return users + .filter((user) => user.name.length > 5) + .map((user) => ({ ...user, displayName: user.name.toUpperCase() })); }, [users]); - const handleUserClick = useCallback((userId: number) => { - onUserSelect(userId); - }, [onUserSelect]); + const handleUserClick = useCallback( + (userId: number) => { + onUserSelect(userId); + }, + [onUserSelect] + ); return (
- {expensiveComputation.map(user => ( + {expensiveComputation.map((user) => (
handleUserClick(user.id)}> {user.displayName}
@@ -2810,8 +2828,9 @@ const TodoList = memo(({ users, onUserSelect }: TodoListProps) => { // After React Compiler (Automatic optimization) function TodoList({ users, onUserSelect }: TodoListProps) { // React Compiler automatically memoizes this computation - const expensiveComputation = users.filter(user => user.name.length > 5) - .map(user => ({ ...user, displayName: user.name.toUpperCase() })); + const expensiveComputation = users + .filter((user) => user.name.length > 5) + .map((user) => ({ ...user, displayName: user.name.toUpperCase() })); // React Compiler automatically memoizes this callback const handleUserClick = (userId: number) => { @@ -2820,7 +2839,7 @@ function TodoList({ users, onUserSelect }: TodoListProps) { return (
- {expensiveComputation.map(user => ( + {expensiveComputation.map((user) => (
handleUserClick(user.id)}> {user.displayName}
@@ -2831,6 +2850,7 @@ function TodoList({ users, onUserSelect }: TodoListProps) { ``` **How to enable React Compiler:** + ```bash # Install React Compiler npm install react-compiler-runtime @@ -2847,6 +2867,7 @@ npm install --save-dev babel-plugin-react-compiler React Server Components (RSC) are components that run on the server and send their rendered output to the client. They enable better performance by reducing the JavaScript bundle size and improving initial page load. **Key Benefits:** + - **Zero Bundle Size**: Server Components don't add to client bundle - **Direct Server Access**: Can directly access databases, file systems - **Automatic Code Splitting**: Only client components are bundled @@ -2854,7 +2875,7 @@ React Server Components (RSC) are components that run on the server and send the ```tsx // Server Component (runs on server) -import { db } from '@/lib/database'; +import { db } from "@/lib/database"; interface BlogPost { id: string; @@ -2867,13 +2888,13 @@ interface BlogPost { async function BlogList() { // Direct database access - only runs on server const posts: BlogPost[] = await db.posts.findMany({ - orderBy: { createdAt: 'desc' } + orderBy: { createdAt: "desc" }, }); return (

Latest Blog Posts

- {posts.map(post => ( + {posts.map((post) => (

{post.title}

By {post.author}

@@ -2885,16 +2906,16 @@ async function BlogList() { } // Client Component (runs in browser) -'use client'; +("use client"); -import { useState } from 'react'; +import { useState } from "react"; interface SearchBarProps { onSearch: (query: string) => void; } function SearchBar({ onSearch }: SearchBarProps) { - const [query, setQuery] = useState(''); + const [query, setQuery] = useState(""); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -2920,7 +2941,7 @@ function BlogPage() {
{/* This runs on the client for interactivity */} console.log(query)} /> - + {/* This runs on the server for data fetching */}
@@ -2929,6 +2950,7 @@ function BlogPage() { ``` **File Structure Example:** + ``` app/ โ”œโ”€โ”€ layout.tsx // Server Component @@ -2951,6 +2973,7 @@ app/ The App Router is a new routing system in Next.js 13+ that leverages React Server Components and provides a more intuitive file-based routing system. **Key Features:** + - **File-based routing** with `app/` directory - **Layouts** and **Templates** for shared UI - **Server Components** by default @@ -2959,11 +2982,11 @@ The App Router is a new routing system in Next.js 13+ that leverages React Serve ```tsx // app/layout.tsx - Root Layout (Server Component) -import './globals.css'; +import "./globals.css"; export const metadata = { - title: 'My App', - description: 'A Next.js 13+ application', + title: "My App", + description: "A Next.js 13+ application", }; interface RootLayoutProps { @@ -3005,13 +3028,15 @@ export default function BlogLayout({ children }: BlogLayoutProps) { -
- {children} -
+
{children}
); } @@ -3023,8 +3048,9 @@ interface BlogPostPageProps { export default async function BlogPostPage({ params }: BlogPostPageProps) { // This runs on the server - const post = await fetch(`https://api.example.com/posts/${params.slug}`) - .then(res => res.json()); + const post = await fetch(`https://api.example.com/posts/${params.slug}`).then( + (res) => res.json() + ); return (
@@ -3045,7 +3071,7 @@ export default function Loading() { } // app/blog/[slug]/error.tsx - Error UI -'use client'; +("use client"); interface ErrorPageProps { error: Error; @@ -3064,6 +3090,7 @@ export default function Error({ error, reset }: ErrorPageProps) { ``` **Directory Structure:** + ``` app/ โ”œโ”€โ”€ layout.tsx // Root layout @@ -3099,6 +3126,7 @@ app/ Understanding when and how to use these optimization techniques is crucial for building performant React applications. **React.memo - Component Memoization:** + ```tsx // TypeScript interface for props interface UserCardProps { @@ -3112,8 +3140,8 @@ interface UserCardProps { // Memoized component - only re-renders when props change const UserCard = React.memo(({ user, onEdit }: UserCardProps) => { - console.log('UserCard rendered for:', user.name); - + console.log("UserCard rendered for:", user.name); + return (

{user.name}

@@ -3146,6 +3174,7 @@ const UserCardWithCustomComparison = React.memo( ``` **useMemo - Value Memoization:** + ```tsx interface Product { id: number; @@ -3157,21 +3186,22 @@ interface Product { interface ProductListProps { products: Product[]; filter: string; - sortBy: 'name' | 'price'; + sortBy: "name" | "price"; } function ProductList({ products, filter, sortBy }: ProductListProps) { // Expensive computation - only runs when dependencies change const filteredAndSortedProducts = useMemo(() => { - console.log('Computing filtered and sorted products'); - + console.log("Computing filtered and sorted products"); + return products - .filter(product => - product.name.toLowerCase().includes(filter.toLowerCase()) || - product.category.toLowerCase().includes(filter.toLowerCase()) + .filter( + (product) => + product.name.toLowerCase().includes(filter.toLowerCase()) || + product.category.toLowerCase().includes(filter.toLowerCase()) ) .sort((a, b) => { - if (sortBy === 'name') { + if (sortBy === "name") { return a.name.localeCompare(b.name); } return a.price - b.price; @@ -3181,7 +3211,7 @@ function ProductList({ products, filter, sortBy }: ProductListProps) { // Memoized calculation const totalValue = useMemo(() => { return filteredAndSortedProducts.reduce( - (sum, product) => sum + product.price, + (sum, product) => sum + product.price, 0 ); }, [filteredAndSortedProducts]); @@ -3189,7 +3219,7 @@ function ProductList({ products, filter, sortBy }: ProductListProps) { return (

Products (Total: ${totalValue.toFixed(2)})

- {filteredAndSortedProducts.map(product => ( + {filteredAndSortedProducts.map((product) => (

{product.name}

${product.price}

@@ -3201,6 +3231,7 @@ function ProductList({ products, filter, sortBy }: ProductListProps) { ``` **useCallback - Function Memoization:** + ```tsx interface TodoListProps { todos: Array<{ @@ -3211,32 +3242,33 @@ interface TodoListProps { } function TodoList({ todos }: TodoListProps) { - const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all'); + const [filter, setFilter] = useState<"all" | "active" | "completed">("all"); // Memoized callback - prevents child re-renders const handleToggleTodo = useCallback((todoId: number) => { - setTodos(prevTodos => - prevTodos.map(todo => - todo.id === todoId - ? { ...todo, completed: !todo.completed } - : todo + setTodos((prevTodos) => + prevTodos.map((todo) => + todo.id === todoId ? { ...todo, completed: !todo.completed } : todo ) ); }, []); // Empty dependency array since setTodos is stable // Memoized callback with dependencies - const handleFilterChange = useCallback((newFilter: 'all' | 'active' | 'completed') => { - console.log('Filter changed to:', newFilter); - setFilter(newFilter); - }, []); + const handleFilterChange = useCallback( + (newFilter: "all" | "active" | "completed") => { + console.log("Filter changed to:", newFilter); + setFilter(newFilter); + }, + [] + ); // Memoized filtered todos const filteredTodos = useMemo(() => { switch (filter) { - case 'active': - return todos.filter(todo => !todo.completed); - case 'completed': - return todos.filter(todo => todo.completed); + case "active": + return todos.filter((todo) => !todo.completed); + case "completed": + return todos.filter((todo) => todo.completed); default: return todos; } @@ -3248,40 +3280,40 @@ function TodoList({ todos }: TodoListProps) { currentFilter={filter} onFilterChange={handleFilterChange} /> - {filteredTodos.map(todo => ( - + {filteredTodos.map((todo) => ( + ))}
); } // Child component that benefits from memoized props -const TodoItem = React.memo(({ - todo, - onToggle -}: { - todo: { id: number; text: string; completed: boolean }; - onToggle: (id: number) => void; -}) => { - console.log('TodoItem rendered:', todo.text); - - return ( -
- onToggle(todo.id)} - /> - - {todo.text} - -
- ); -}); +const TodoItem = React.memo( + ({ + todo, + onToggle, + }: { + todo: { id: number; text: string; completed: boolean }; + onToggle: (id: number) => void; + }) => { + console.log("TodoItem rendered:", todo.text); + + return ( +
+ onToggle(todo.id)} + /> + + {todo.text} + +
+ ); + } +); ``` **When to use each:** @@ -3291,12 +3323,13 @@ const TodoItem = React.memo(({ - **useCallback**: For functions passed as props to memoized components **Anti-patterns to avoid:** + ```tsx // โŒ Don't memoize everything const OverOptimized = React.memo(() => { const simpleValue = useMemo(() => 1 + 1, []); // Unnecessary const simpleCallback = useCallback(() => { - console.log('hello'); + console.log("hello"); }, []); // May not be worth it return
{simpleValue}
; @@ -3322,6 +3355,7 @@ const WellOptimized = () => { Here are advanced TypeScript patterns for React development with proper type safety and reusability. **Generic Components:** + ```tsx // Generic List Component interface ListItem { @@ -3339,7 +3373,7 @@ function List({ items, renderItem, keyExtractor = (item) => item.id, - emptyMessage = 'No items found' + emptyMessage = "No items found", }: ListProps) { if (items.length === 0) { return
{emptyMessage}
; @@ -3370,13 +3404,9 @@ interface Product { } function App() { - const users: User[] = [ - { id: 1, name: 'John', email: 'john@example.com' } - ]; + const users: User[] = [{ id: 1, name: "John", email: "john@example.com" }]; - const products: Product[] = [ - { id: 'prod-1', title: 'Laptop', price: 999 } - ]; + const products: Product[] = [{ id: "prod-1", title: "Laptop", price: 999 }]; return (
@@ -3406,6 +3436,7 @@ function App() { ``` **Advanced Hook Types:** + ```tsx // Custom hook with proper TypeScript interface UseApiResult { @@ -3424,17 +3455,17 @@ function useApi(url: string, options?: RequestInit): UseApiResult { 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 instanceof Error ? err.message : 'An error occurred'); + setError(err instanceof Error ? err.message : "An error occurred"); } finally { setLoading(false); } @@ -3456,9 +3487,12 @@ interface ApiUser { } function UserProfile({ userId }: { userId: number }) { - const { data: user, loading, error, refetch } = useApi( - `/api/users/${userId}` - ); + const { + data: user, + loading, + error, + refetch, + } = useApi(`/api/users/${userId}`); if (loading) return
Loading user...
; if (error) return
Error: {error}
; @@ -3476,6 +3510,7 @@ function UserProfile({ userId }: { userId: number }) { ``` **Form Handling with TypeScript:** + ```tsx // Type-safe form handling interface FormData { @@ -3489,12 +3524,12 @@ type FormErrors = Partial>; interface UseFormResult { values: T; errors: Partial>; - handleChange: (field: keyof T) => ( - event: React.ChangeEvent - ) => void; - handleSubmit: (onSubmit: (values: T) => void) => ( - event: React.FormEvent - ) => void; + handleChange: ( + field: keyof T + ) => (event: React.ChangeEvent) => void; + handleSubmit: ( + onSubmit: (values: T) => void + ) => (event: React.FormEvent) => void; setFieldError: (field: keyof T, error: string) => void; clearErrors: () => void; } @@ -3506,42 +3541,44 @@ function useForm>( const [values, setValues] = useState(initialValues); const [errors, setErrors] = useState>>({}); - const handleChange = useCallback((field: keyof T) => - (event: React.ChangeEvent) => { + const handleChange = useCallback( + (field: keyof T) => (event: React.ChangeEvent) => { const { value, type, checked } = event.target; - setValues(prev => ({ + setValues((prev) => ({ ...prev, - [field]: type === 'checkbox' ? checked : value + [field]: type === "checkbox" ? checked : value, })); - + // Clear error when user starts typing if (errors[field]) { - setErrors(prev => ({ ...prev, [field]: undefined })); + setErrors((prev) => ({ ...prev, [field]: undefined })); } - }, [errors] + }, + [errors] ); const setFieldError = useCallback((field: keyof T, error: string) => { - setErrors(prev => ({ ...prev, [field]: error })); + setErrors((prev) => ({ ...prev, [field]: error })); }, []); const clearErrors = useCallback(() => { setErrors({}); }, []); - const handleSubmit = useCallback((onSubmit: (values: T) => void) => - (event: React.FormEvent) => { + const handleSubmit = useCallback( + (onSubmit: (values: T) => void) => (event: React.FormEvent) => { event.preventDefault(); - + const validationErrors = validator?.(values) || {}; - + if (Object.keys(validationErrors).length > 0) { setErrors(validationErrors); return; } - + onSubmit(values); - }, [values, validator] + }, + [values, validator] ); return { @@ -3550,7 +3587,7 @@ function useForm>( handleChange, handleSubmit, setFieldError, - clearErrors + clearErrors, }; } @@ -3558,49 +3595,44 @@ function useForm>( function LoginForm() { const validateForm = (values: FormData): FormErrors => { const errors: FormErrors = {}; - + if (!values.email) { - errors.email = 'Email is required'; + errors.email = "Email is required"; } else if (!/\S+@\S+\.\S+/.test(values.email)) { - errors.email = 'Email is invalid'; + errors.email = "Email is invalid"; } - + if (!values.password) { - errors.password = 'Password is required'; + errors.password = "Password is required"; } else if (values.password.length < 6) { - errors.password = 'Password must be at least 6 characters'; + errors.password = "Password must be at least 6 characters"; } - + return errors; }; - const { - values, - errors, - handleChange, - handleSubmit, - setFieldError - } = useForm( - { email: '', password: '', rememberMe: false }, - validateForm - ); + const { values, errors, handleChange, handleSubmit, setFieldError } = + useForm( + { email: "", password: "", rememberMe: false }, + validateForm + ); const onSubmit = async (formData: FormData) => { try { - const response = await fetch('/api/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(formData) + const response = await fetch("/api/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(formData), }); if (!response.ok) { - setFieldError('email', 'Invalid credentials'); + setFieldError("email", "Invalid credentials"); return; } - console.log('Login successful!'); + console.log("Login successful!"); } catch (error) { - setFieldError('email', 'Network error occurred'); + setFieldError("email", "Network error occurred"); } }; @@ -3612,7 +3644,7 @@ function LoginForm() { id="email" type="email" value={values.email} - onChange={handleChange('email')} + onChange={handleChange("email")} /> {errors.email && {errors.email}}
@@ -3623,7 +3655,7 @@ function LoginForm() { id="password" type="password" value={values.password} - onChange={handleChange('password')} + onChange={handleChange("password")} /> {errors.password && {errors.password}}
@@ -3633,7 +3665,7 @@ function LoginForm() { Remember me @@ -3646,10 +3678,11 @@ function LoginForm() { ``` **Component Props with Variants:** + ```tsx // Button component with variants -type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'outline'; -type ButtonSize = 'sm' | 'md' | 'lg'; +type ButtonVariant = "primary" | "secondary" | "danger" | "outline"; +type ButtonSize = "sm" | "md" | "lg"; interface BaseButtonProps { variant?: ButtonVariant; @@ -3659,31 +3692,36 @@ interface BaseButtonProps { children: React.ReactNode; } -type ButtonProps = BaseButtonProps & +type ButtonProps = BaseButtonProps & React.ButtonHTMLAttributes; const Button = React.forwardRef( - ({ - variant = 'primary', - size = 'md', - loading = false, - disabled = false, - children, - className = '', - ...rest - }, ref) => { - const baseClasses = 'btn'; + ( + { + variant = "primary", + size = "md", + loading = false, + disabled = false, + children, + className = "", + ...rest + }, + ref + ) => { + const baseClasses = "btn"; const variantClasses = `btn--${variant}`; const sizeClasses = `btn--${size}`; - const loadingClasses = loading ? 'btn--loading' : ''; - + const loadingClasses = loading ? "btn--loading" : ""; + const buttonClasses = [ baseClasses, variantClasses, sizeClasses, loadingClasses, - className - ].filter(Boolean).join(' '); + className, + ] + .filter(Boolean) + .join(" "); return ( ); } ); -Button.displayName = 'Button'; +Button.displayName = "Button"; // Usage function App() { return (
- - + - + @@ -3738,6 +3776,7 @@ function App() { React Testing Library with TypeScript provides excellent tools for testing React components in a way that resembles how users interact with your application. **Basic Component Testing:** + ```tsx // UserCard.tsx interface User { @@ -3758,8 +3797,8 @@ export function UserCard({ user, onEdit, onDelete }: UserCardProps) {

{user.name}

{user.email}

- - {user.isActive ? 'Active' : 'Inactive'} + + {user.isActive ? "Active" : "Inactive"} @@ -3768,17 +3807,17 @@ export function UserCard({ user, onEdit, onDelete }: UserCardProps) { } // UserCard.test.tsx -import { render, screen, fireEvent } from '@testing-library/react'; -import { UserCard } from './UserCard'; +import { render, screen, fireEvent } from "@testing-library/react"; +import { UserCard } from "./UserCard"; const mockUser: User = { id: 1, - name: 'John Doe', - email: 'john@example.com', - isActive: true + name: "John Doe", + email: "john@example.com", + isActive: true, }; -describe('UserCard', () => { +describe("UserCard", () => { const mockOnEdit = jest.fn(); const mockOnDelete = jest.fn(); @@ -3787,68 +3826,57 @@ describe('UserCard', () => { mockOnDelete.mockClear(); }); - it('renders user information correctly', () => { + it("renders user information correctly", () => { render( - + ); - expect(screen.getByText('John Doe')).toBeInTheDocument(); - expect(screen.getByText('john@example.com')).toBeInTheDocument(); - expect(screen.getByText('Active')).toBeInTheDocument(); + expect(screen.getByText("John Doe")).toBeInTheDocument(); + expect(screen.getByText("john@example.com")).toBeInTheDocument(); + expect(screen.getByText("Active")).toBeInTheDocument(); }); - it('calls onEdit when edit button is clicked', () => { + it("calls onEdit when edit button is clicked", () => { render( - + ); - fireEvent.click(screen.getByText('Edit')); + fireEvent.click(screen.getByText("Edit")); expect(mockOnEdit).toHaveBeenCalledWith(1); expect(mockOnEdit).toHaveBeenCalledTimes(1); }); - it('calls onDelete when delete button is clicked', () => { + it("calls onDelete when delete button is clicked", () => { render( - + ); - fireEvent.click(screen.getByText('Delete')); + fireEvent.click(screen.getByText("Delete")); expect(mockOnDelete).toHaveBeenCalledWith(1); }); - it('displays correct status for inactive user', () => { + it("displays correct status for inactive user", () => { const inactiveUser = { ...mockUser, isActive: false }; - + render( - ); - expect(screen.getByText('Inactive')).toBeInTheDocument(); - expect(screen.getByText('Inactive')).toHaveClass('inactive'); + expect(screen.getByText("Inactive")).toBeInTheDocument(); + expect(screen.getByText("Inactive")).toHaveClass("inactive"); }); }); ``` **Testing Custom Hooks:** + ```tsx // useCounter.ts -import { useState, useCallback } from 'react'; +import { useState, useCallback } from "react"; interface UseCounterResult { count: number; @@ -3862,11 +3890,11 @@ export function useCounter(initialValue: number = 0): UseCounterResult { const [count, setCount] = useState(initialValue); const increment = useCallback(() => { - setCount(prev => prev + 1); + setCount((prev) => prev + 1); }, []); const decrement = useCallback(() => { - setCount(prev => prev - 1); + setCount((prev) => prev - 1); }, []); const reset = useCallback(() => { @@ -3878,81 +3906,82 @@ export function useCounter(initialValue: number = 0): UseCounterResult { increment, decrement, reset, - setCount + setCount, }; } // useCounter.test.ts -import { renderHook, act } from '@testing-library/react'; -import { useCounter } from './useCounter'; +import { renderHook, act } from "@testing-library/react"; +import { useCounter } from "./useCounter"; -describe('useCounter', () => { - it('initializes with default value', () => { +describe("useCounter", () => { + it("initializes with default value", () => { const { result } = renderHook(() => useCounter()); expect(result.current.count).toBe(0); }); - it('initializes with custom value', () => { + it("initializes with custom value", () => { const { result } = renderHook(() => useCounter(10)); expect(result.current.count).toBe(10); }); - it('increments count', () => { + it("increments count", () => { const { result } = renderHook(() => useCounter(5)); - + act(() => { result.current.increment(); }); - + expect(result.current.count).toBe(6); }); - it('decrements count', () => { + it("decrements count", () => { const { result } = renderHook(() => useCounter(5)); - + act(() => { result.current.decrement(); }); - + expect(result.current.count).toBe(4); }); - it('resets to initial value', () => { + it("resets to initial value", () => { const { result } = renderHook(() => useCounter(10)); - + act(() => { result.current.increment(); result.current.increment(); }); - + expect(result.current.count).toBe(12); - + act(() => { result.current.reset(); }); - + expect(result.current.count).toBe(10); }); - it('sets count to specific value', () => { + it("sets count to specific value", () => { const { result } = renderHook(() => useCounter()); - + act(() => { result.current.setCount(25); }); - + expect(result.current.count).toBe(25); }); }); ``` **Testing Components with Context:** + ```tsx // AuthContext.tsx interface User { id: string; name: string; - role: 'admin' | 'user'; + role: "admin" | "user"; } interface AuthContextType { @@ -3975,7 +4004,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { setUser(null); }, []); - const isAdmin = user?.role === 'admin'; + const isAdmin = user?.role === "admin"; return ( @@ -3987,7 +4016,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { export function useAuth() { const context = useContext(AuthContext); if (!context) { - throw new Error('useAuth must be used within AuthProvider'); + throw new Error("useAuth must be used within AuthProvider"); } return context; } @@ -4010,66 +4039,59 @@ export function AdminPanel() { } // AdminPanel.test.tsx -import { render, screen, fireEvent } from '@testing-library/react'; -import { AuthProvider } from './AuthContext'; -import { AdminPanel } from './AdminPanel'; +import { render, screen, fireEvent } from "@testing-library/react"; +import { AuthProvider } from "./AuthContext"; +import { AdminPanel } from "./AdminPanel"; const renderWithAuth = (ui: React.ReactElement, { user = null } = {}) => { function Wrapper({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); + return {children}; } const result = render(ui, { wrapper: Wrapper }); - + // If user is provided, log them in if (user) { - const authContext = result.container.querySelector('[data-testid="auth-provider"]'); + const authContext = result.container.querySelector( + '[data-testid="auth-provider"]' + ); // In real implementation, you'd expose the login function through a test helper } - + return result; }; // Custom render helper with authenticated user -const renderWithAuthenticatedUser = ( - ui: React.ReactElement, - user: User -) => { +const renderWithAuthenticatedUser = (ui: React.ReactElement, user: User) => { function AuthWrapper({ children }: { children: React.ReactNode }) { return ( -
- {children} -
+
{children}
); } const result = render(ui, { wrapper: AuthWrapper }); - + // You would typically expose a way to set the user in tests // This is a simplified example return result; }; -describe('AdminPanel', () => { +describe("AdminPanel", () => { const adminUser: User = { - id: '1', - name: 'Admin User', - role: 'admin' + id: "1", + name: "Admin User", + role: "admin", }; const regularUser: User = { - id: '2', - name: 'Regular User', - role: 'user' + id: "2", + name: "Regular User", + role: "user", }; - it('denies access for non-admin users', () => { + it("denies access for non-admin users", () => { // This test would need proper setup with the context render( @@ -4077,10 +4099,12 @@ describe('AdminPanel', () => { ); - expect(screen.getByText('Access denied. Admin rights required.')).toBeInTheDocument(); + expect( + screen.getByText("Access denied. Admin rights required.") + ).toBeInTheDocument(); }); - it('shows admin panel for admin users', () => { + it("shows admin panel for admin users", () => { // You would set up the authenticated admin user here // This is a simplified test structure }); @@ -4088,6 +4112,7 @@ describe('AdminPanel', () => { ``` **Testing Async Components:** + ```tsx // UserList.tsx interface User { @@ -4104,14 +4129,14 @@ export function UserList() { useEffect(() => { const fetchUsers = async () => { try { - const response = await fetch('/api/users'); + const response = await fetch("/api/users"); if (!response.ok) { - throw new Error('Failed to fetch users'); + throw new Error("Failed to fetch users"); } const userData = await response.json(); setUsers(userData); } catch (err) { - setError(err instanceof Error ? err.message : 'Unknown error'); + setError(err instanceof Error ? err.message : "Unknown error"); } finally { setLoading(false); } @@ -4126,7 +4151,7 @@ export function UserList() { return (

Users

- {users.map(user => ( + {users.map((user) => (

{user.name}

{user.email}

@@ -4137,53 +4162,53 @@ export function UserList() { } // UserList.test.tsx -import { render, screen, waitFor } from '@testing-library/react'; -import { UserList } from './UserList'; +import { render, screen, waitFor } from "@testing-library/react"; +import { UserList } from "./UserList"; // Mock fetch global.fetch = jest.fn(); const mockFetch = fetch as jest.MockedFunction; const mockUsers: User[] = [ - { id: 1, name: 'John Doe', email: 'john@example.com' }, - { id: 2, name: 'Jane Smith', email: 'jane@example.com' } + { id: 1, name: "John Doe", email: "john@example.com" }, + { id: 2, name: "Jane Smith", email: "jane@example.com" }, ]; -describe('UserList', () => { +describe("UserList", () => { beforeEach(() => { mockFetch.mockClear(); }); - it('displays loading state initially', () => { + it("displays loading state initially", () => { mockFetch.mockReturnValue( Promise.resolve({ ok: true, - json: () => Promise.resolve(mockUsers) + json: () => Promise.resolve(mockUsers), } as Response) ); render(); - expect(screen.getByText('Loading users...')).toBeInTheDocument(); + expect(screen.getByText("Loading users...")).toBeInTheDocument(); }); - it('displays users after successful fetch', async () => { + it("displays users after successful fetch", async () => { mockFetch.mockResolvedValue({ ok: true, - json: () => Promise.resolve(mockUsers) + json: () => Promise.resolve(mockUsers), } as Response); render(); await waitFor(() => { - expect(screen.getByText('Users')).toBeInTheDocument(); + expect(screen.getByText("Users")).toBeInTheDocument(); }); - expect(screen.getByText('John Doe')).toBeInTheDocument(); - expect(screen.getByText('jane@example.com')).toBeInTheDocument(); + expect(screen.getByText("John Doe")).toBeInTheDocument(); + expect(screen.getByText("jane@example.com")).toBeInTheDocument(); }); - it('displays error message on fetch failure', async () => { - mockFetch.mockRejectedValue(new Error('Network error')); + it("displays error message on fetch failure", async () => { + mockFetch.mockRejectedValue(new Error("Network error")); render(); @@ -4192,29 +4217,32 @@ describe('UserList', () => { }); }); - it('handles HTTP error responses', async () => { + it("handles HTTP error responses", async () => { mockFetch.mockResolvedValue({ ok: false, - status: 404 + status: 404, } as Response); render(); await waitFor(() => { - expect(screen.getByText(/Error: Failed to fetch users/)).toBeInTheDocument(); + expect( + screen.getByText(/Error: Failed to fetch users/) + ).toBeInTheDocument(); }); }); }); ``` **Test Setup Configuration:** + ```typescript // setupTests.ts -import '@testing-library/jest-dom'; -import { configure } from '@testing-library/react'; +import "@testing-library/jest-dom"; +import { configure } from "@testing-library/react"; // Configure testing library -configure({ testIdAttribute: 'data-testid' }); +configure({ testIdAttribute: "data-testid" }); // Mock IntersectionObserver global.IntersectionObserver = class IntersectionObserver { @@ -4247,6 +4275,7 @@ global.ResizeObserver = class ResizeObserver { React applications can be vulnerable to several security threats. Here are the most common ones and how to prevent them: **1. Cross-Site Scripting (XSS):** + ```tsx // โŒ Dangerous - allows XSS attacks interface UnsafeComponentProps { @@ -4259,7 +4288,7 @@ function UnsafeComponent({ userContent }: UnsafeComponentProps) { } // โœ… Safe approaches -import DOMPurify from 'dompurify'; +import DOMPurify from "dompurify"; function SafeComponent({ userContent }: UnsafeComponentProps) { // Approach 1: Use React's built-in XSS protection @@ -4282,6 +4311,7 @@ function CommentComponent({ comment }: { comment: string }) { ``` **2. Dependency Vulnerabilities:** + ```bash # Regular security audits npm audit @@ -4297,6 +4327,7 @@ npm outdated ``` **3. Environment Variables Security:** + ```typescript // โŒ Don't expose sensitive data in client-side code const API_KEY = process.env.REACT_APP_SECRET_API_KEY; // Exposed to client! @@ -4308,16 +4339,17 @@ const PUBLIC_API_URL = process.env.REACT_APP_PUBLIC_API_URL; // Keep sensitive data on the server // server-side API call async function getSecureData() { - const response = await fetch('/api/secure-endpoint', { + const response = await fetch("/api/secure-endpoint", { headers: { - 'Authorization': `Bearer ${await getAuthToken()}` // Server-side token - } + Authorization: `Bearer ${await getAuthToken()}`, // Server-side token + }, }); return response.json(); } ``` **4. Authentication & Authorization:** + ```tsx // Secure authentication implementation interface AuthToken { @@ -4327,8 +4359,8 @@ interface AuthToken { } class AuthService { - private static readonly TOKEN_KEY = 'auth_token'; - private static readonly REFRESH_KEY = 'refresh_token'; + private static readonly TOKEN_KEY = "auth_token"; + private static readonly REFRESH_KEY = "refresh_token"; static setTokens(tokens: AuthToken): void { // Store tokens securely (consider httpOnly cookies for production) @@ -4346,7 +4378,7 @@ class AuthService { static isTokenValid(token: string): boolean { try { - const payload = JSON.parse(atob(token.split('.')[1])); + const payload = JSON.parse(atob(token.split(".")[1])); return payload.exp * 1000 > Date.now(); } catch { return false; @@ -4358,10 +4390,10 @@ class AuthService { if (!refreshToken) return null; try { - const response = await fetch('/api/auth/refresh', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ refreshToken }) + const response = await fetch("/api/auth/refresh", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ refreshToken }), }); if (response.ok) { @@ -4370,7 +4402,7 @@ class AuthService { return newTokens.token; } } catch (error) { - console.error('Token refresh failed:', error); + console.error("Token refresh failed:", error); } return null; @@ -4383,12 +4415,12 @@ class AuthService { } // Protected Route Component -function ProtectedRoute({ - children, - requiredRole -}: { - children: React.ReactNode; - requiredRole?: string +function ProtectedRoute({ + children, + requiredRole, +}: { + children: React.ReactNode; + requiredRole?: string; }) { const { user, isAuthenticated } = useAuth(); @@ -4405,26 +4437,29 @@ function ProtectedRoute({ ``` **5. CSRF Protection:** + ```tsx // CSRF token handling function CSRFProtectedForm() { - const [csrfToken, setCsrfToken] = useState(''); + const [csrfToken, setCsrfToken] = useState(""); useEffect(() => { // Get CSRF token from meta tag or API - const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'); - setCsrfToken(token || ''); + const token = document + .querySelector('meta[name="csrf-token"]') + ?.getAttribute("content"); + setCsrfToken(token || ""); }, []); const handleSubmit = async (formData: any) => { - await fetch('/api/protected-endpoint', { - method: 'POST', + await fetch("/api/protected-endpoint", { + method: "POST", headers: { - 'Content-Type': 'application/json', - 'X-CSRF-Token': csrfToken, // Include CSRF token - 'Authorization': `Bearer ${AuthService.getToken()}` + "Content-Type": "application/json", + "X-CSRF-Token": csrfToken, // Include CSRF token + Authorization: `Bearer ${AuthService.getToken()}`, }, - body: JSON.stringify(formData) + body: JSON.stringify(formData), }); }; @@ -4438,17 +4473,21 @@ function CSRFProtectedForm() { ``` **6. Content Security Policy (CSP):** + ```html - + connect-src 'self' https://api.yourapp.com;" +/> ``` **7. Input Validation:** + ```tsx // Comprehensive input validation interface FormValidation { @@ -4459,19 +4498,19 @@ interface FormValidation { const validation: FormValidation = { email: (value: string) => { - if (!value) return 'Email is required'; + if (!value) return "Email is required"; if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { - return 'Please enter a valid email address'; + return "Please enter a valid email address"; } - if (value.length > 254) return 'Email is too long'; + if (value.length > 254) return "Email is too long"; return null; }, password: (value: string) => { - if (!value) return 'Password is required'; - if (value.length < 8) return 'Password must be at least 8 characters'; + if (!value) return "Password is required"; + if (value.length < 8) return "Password must be at least 8 characters"; if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) { - return 'Password must contain uppercase, lowercase, and number'; + return "Password must contain uppercase, lowercase, and number"; } return null; }, @@ -4480,22 +4519,22 @@ const validation: FormValidation = { if (!value) return null; // Optional field try { const url = new URL(value); - if (!['http:', 'https:'].includes(url.protocol)) { - return 'URL must use HTTP or HTTPS protocol'; + if (!["http:", "https:"].includes(url.protocol)) { + return "URL must use HTTP or HTTPS protocol"; } return null; } catch { - return 'Please enter a valid URL'; + return "Please enter a valid URL"; } - } + }, }; // Secure form component function SecureForm() { const [formData, setFormData] = useState({ - email: '', - password: '', - website: '' + email: "", + password: "", + website: "", }); const [errors, setErrors] = useState>({}); @@ -4506,20 +4545,20 @@ function SecureForm() { const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; - + // Sanitize input const sanitizedValue = value.trim().slice(0, 1000); // Limit length - - setFormData(prev => ({ ...prev, [name]: sanitizedValue })); - + + setFormData((prev) => ({ ...prev, [name]: sanitizedValue })); + // Real-time validation const error = validateField(name, sanitizedValue); - setErrors(prev => ({ ...prev, [name]: error || '' })); + setErrors((prev) => ({ ...prev, [name]: error || "" })); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - + // Validate all fields const newErrors: Record = {}; Object.entries(formData).forEach(([key, value]) => { @@ -4549,9 +4588,13 @@ function SecureForm() { aria-invalid={!!errors.email} aria-describedby={errors.email ? "email-error" : undefined} /> - {errors.email && } + {errors.email && ( + + )}
- +
{errors.password &&
{errors.password}
}
- + ); @@ -4581,6 +4624,7 @@ function SecureForm() { Accessibility is crucial for making React applications usable by everyone. Here are key techniques and patterns: **1. Semantic HTML and ARIA:** + ```tsx // Good semantic structure interface NavigationProps { @@ -4595,8 +4639,8 @@ function AccessibleNavigation({ items }: NavigationProps) {
  • {item.label} @@ -4623,19 +4667,19 @@ function AccessibleModal({ isOpen, onClose, title, children }: ModalProps) { if (isOpen) { // Store current focus previousFocusRef.current = document.activeElement as HTMLElement; - + // Focus modal modalRef.current?.focus(); - + // Trap focus within modal const trapFocus = (e: KeyboardEvent) => { - if (e.key === 'Escape') { + if (e.key === "Escape") { onClose(); } }; - - document.addEventListener('keydown', trapFocus); - return () => document.removeEventListener('keydown', trapFocus); + + document.addEventListener("keydown", trapFocus); + return () => document.removeEventListener("keydown", trapFocus); } else { // Return focus to previous element previousFocusRef.current?.focus(); @@ -4667,9 +4711,7 @@ function AccessibleModal({ isOpen, onClose, title, children }: ModalProps) { ร—
  • -
    - {children} -
    +
    {children}
    ); @@ -4677,12 +4719,13 @@ function AccessibleModal({ isOpen, onClose, title, children }: ModalProps) { ``` **2. Form Accessibility:** + ```tsx // Comprehensive accessible form interface FormField { id: string; label: string; - type: 'text' | 'email' | 'password' | 'tel'; + type: "text" | "email" | "password" | "tel"; required?: boolean; placeholder?: string; autoComplete?: string; @@ -4697,16 +4740,16 @@ function AccessibleForm({ fields, onSubmit }: AccessibleFormProps) { const [formData, setFormData] = useState>({}); const [errors, setErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); - + const firstErrorRef = useRef(null); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); - + // Validate form const newErrors: Record = {}; - fields.forEach(field => { + fields.forEach((field) => { if (field.required && !formData[field.id]) { newErrors[field.id] = `${field.label} is required`; } @@ -4723,17 +4766,17 @@ function AccessibleForm({ fields, onSubmit }: AccessibleFormProps) { try { await onSubmit(formData); } catch (error) { - setErrors({ general: 'An error occurred while submitting the form' }); + setErrors({ general: "An error occurred while submitting the form" }); } finally { setIsSubmitting(false); } }; const handleChange = (fieldId: string, value: string) => { - setFormData(prev => ({ ...prev, [fieldId]: value })); + setFormData((prev) => ({ ...prev, [fieldId]: value })); // Clear error when user starts typing if (errors[fieldId]) { - setErrors(prev => ({ ...prev, [fieldId]: '' })); + setErrors((prev) => ({ ...prev, [fieldId]: "" })); } }; @@ -4741,7 +4784,7 @@ function AccessibleForm({ fields, onSubmit }: AccessibleFormProps) {
    Registration Form - + {errors.general && (
    {errors.general} @@ -4754,21 +4797,27 @@ function AccessibleForm({ fields, onSubmit }: AccessibleFormProps) { {field.label} {field.required && *} - + 0 ? firstErrorRef : null} + ref={ + index === 0 && Object.keys(errors).length > 0 + ? firstErrorRef + : null + } id={field.id} type={field.type} - value={formData[field.id] || ''} + value={formData[field.id] || ""} onChange={(e) => handleChange(field.id, e.target.value)} placeholder={field.placeholder} autoComplete={field.autoComplete} required={field.required} aria-invalid={!!errors[field.id]} - aria-describedby={errors[field.id] ? `${field.id}-error` : undefined} - className={errors[field.id] ? 'error' : ''} + aria-describedby={ + errors[field.id] ? `${field.id}-error` : undefined + } + className={errors[field.id] ? "error" : ""} /> - + {errors[field.id] && (
    - {isSubmitting ? 'Submitting...' : 'Submit'} + {isSubmitting ? "Submitting..." : "Submit"} - + {isSubmitting && (
    Form is being submitted @@ -4801,6 +4850,7 @@ function AccessibleForm({ fields, onSubmit }: AccessibleFormProps) { ``` **3. Focus Management:** + ```tsx // Custom hook for focus management function useFocusTrap(isActive: boolean) { @@ -4820,7 +4870,7 @@ function useFocusTrap(isActive: boolean) { const lastElement = focusableElements[focusableElements.length - 1]; const handleTabKey = (e: KeyboardEvent) => { - if (e.key === 'Tab') { + if (e.key === "Tab") { if (e.shiftKey) { if (document.activeElement === firstElement) { lastElement.focus(); @@ -4835,11 +4885,11 @@ function useFocusTrap(isActive: boolean) { } }; - document.addEventListener('keydown', handleTabKey); + document.addEventListener("keydown", handleTabKey); firstElement?.focus(); return () => { - document.removeEventListener('keydown', handleTabKey); + document.removeEventListener("keydown", handleTabKey); }; }, [isActive]); @@ -4862,27 +4912,31 @@ function SkipLinks() { ``` **4. Screen Reader Support:** + ```tsx // Live regions for dynamic content function useAnnouncement() { - const [announcement, setAnnouncement] = useState(''); + const [announcement, setAnnouncement] = useState(""); const announce = useCallback((message: string) => { setAnnouncement(message); // Clear after announcement - setTimeout(() => setAnnouncement(''), 1000); + setTimeout(() => setAnnouncement(""), 1000); }, []); - const AnnouncementRegion = useCallback(() => ( -
    - {announcement} -
    - ), [announcement]); + const AnnouncementRegion = useCallback( + () => ( +
    + {announcement} +
    + ), + [announcement] + ); return { announce, AnnouncementRegion }; } @@ -4892,7 +4946,7 @@ interface TableData { id: string; name: string; email: string; - status: 'active' | 'inactive'; + status: "active" | "inactive"; } interface AccessibleTableProps { @@ -4904,7 +4958,7 @@ function AccessibleTable({ data, caption }: AccessibleTableProps) { const { announce, AnnouncementRegion } = useAnnouncement(); const handleStatusToggle = (userId: string, currentStatus: string) => { - const newStatus = currentStatus === 'active' ? 'inactive' : 'active'; + const newStatus = currentStatus === "active" ? "inactive" : "active"; // Update logic here announce(`User status changed to ${newStatus}`); }; @@ -4915,10 +4969,18 @@ function AccessibleTable({ data, caption }: AccessibleTableProps) { {caption} - Name - Email - Status - Actions + + Name + + + Email + + + Status + + + Actions + @@ -4939,8 +5001,8 @@ function AccessibleTable({ data, caption }: AccessibleTableProps) { Toggle Status
    - Current status: {user.status}. Click to change to{' '} - {user.status === 'active' ? 'inactive' : 'active'} + Current status: {user.status}. Click to change to{" "} + {user.status === "active" ? "inactive" : "active"}
    @@ -4954,6 +5016,7 @@ function AccessibleTable({ data, caption }: AccessibleTableProps) { ``` **5. CSS for Accessibility:** + ```css /* Screen reader only content */ .sr-only { @@ -5009,19 +5072,25 @@ function AccessibleTable({ data, caption }: AccessibleTableProps) { ``` **6. Testing Accessibility:** + ```tsx // Accessibility testing utilities -import { axe, toHaveNoViolations } from 'jest-axe'; +import { axe, toHaveNoViolations } from "jest-axe"; expect.extend(toHaveNoViolations); -describe('AccessibleForm', () => { - it('should have no accessibility violations', async () => { +describe("AccessibleForm", () => { + it("should have no accessibility violations", async () => { const { container } = render( - @@ -5031,18 +5100,18 @@ describe('AccessibleForm', () => { expect(results).toHaveNoViolations(); }); - it('should have proper ARIA labels', () => { + it("should have proper ARIA labels", () => { render( - ); - expect(screen.getByLabelText('Email *')).toBeInTheDocument(); - expect(screen.getByRole('textbox', { name: /email/i })).toBeInTheDocument(); + expect(screen.getByLabelText("Email *")).toBeInTheDocument(); + expect(screen.getByRole("textbox", { name: /email/i })).toBeInTheDocument(); }); }); ``` @@ -5072,7 +5141,9 @@ const AccordionContext = createContext(null); function useAccordion() { const context = useContext(AccordionContext); if (!context) { - throw new Error('Accordion compound components must be used within Accordion'); + throw new Error( + "Accordion compound components must be used within Accordion" + ); } return context; } @@ -5084,8 +5155,14 @@ interface AccordionProps { allowMultiple?: boolean; } -function Accordion({ children, defaultActiveIndex = null, allowMultiple = false }: AccordionProps) { - const [activeIndex, setActiveIndex] = useState(defaultActiveIndex); +function Accordion({ + children, + defaultActiveIndex = null, + allowMultiple = false, +}: AccordionProps) { + const [activeIndex, setActiveIndex] = useState( + defaultActiveIndex + ); const handleSetActiveIndex = (index: number | null) => { if (allowMultiple) { @@ -5096,7 +5173,9 @@ function Accordion({ children, defaultActiveIndex = null, allowMultiple = false }; return ( - +
    {children}
    ); @@ -5113,7 +5192,10 @@ function AccordionItem({ children, index }: AccordionItemProps) { const isActive = activeIndex === index; return ( -
    +
    {children}
    ); @@ -5131,14 +5213,14 @@ function AccordionHeader({ children }: AccordionHeaderProps) { useEffect(() => { // Find parent AccordionItem to get index let element = itemElement.current?.parentElement; - while (element && !element.hasAttribute('data-index')) { + while (element && !element.hasAttribute("data-index")) { element = element.parentElement; } itemElement.current = element as HTMLElement; }, []); const handleClick = () => { - const index = itemElement.current?.getAttribute('data-index'); + const index = itemElement.current?.getAttribute("data-index"); if (index !== null && index !== undefined) { setActiveIndex(parseInt(index)); } @@ -5168,12 +5250,12 @@ function AccordionPanel({ children }: AccordionPanelProps) { useEffect(() => { let element = itemElement.current?.parentElement; - while (element && !element.hasAttribute('data-index')) { + while (element && !element.hasAttribute("data-index")) { element = element.parentElement; } - + if (element) { - const index = parseInt(element.getAttribute('data-index') || '0'); + const index = parseInt(element.getAttribute("data-index") || "0"); setIsActive(activeIndex === index); } }, [activeIndex]); @@ -5181,9 +5263,9 @@ function AccordionPanel({ children }: AccordionPanelProps) { return (
    {children} @@ -5210,14 +5292,16 @@ function App() { What is TypeScript? - TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. + TypeScript is a typed superset of JavaScript that compiles to plain + JavaScript. What are React Hooks? - Hooks are functions that let you use state and other React features without writing a class. + Hooks are functions that let you use state and other React features + without writing a class. @@ -5241,9 +5325,9 @@ interface TabsProviderProps { defaultTab?: string; } -function TabsProvider({ children, defaultTab = '' }: TabsProviderProps) { +function TabsProvider({ children, defaultTab = "" }: TabsProviderProps) { const [activeTab, setActiveTab] = useState(defaultTab); - + return ( {children} @@ -5270,7 +5354,11 @@ interface TabListProps { } function TabList({ children }: TabListProps) { - return
    {children}
    ; + return ( +
    + {children} +
    + ); } interface TabProps { @@ -5281,8 +5369,8 @@ interface TabProps { function Tab({ tabId, children, disabled = false }: TabProps) { const context = useContext(TabsContext); - if (!context) throw new Error('Tab must be used within Tabs'); - + if (!context) throw new Error("Tab must be used within Tabs"); + const { activeTab, setActiveTab } = context; const isActive = activeTab === tabId; @@ -5292,7 +5380,7 @@ function Tab({ tabId, children, disabled = false }: TabProps) { aria-selected={isActive} aria-controls={`panel-${tabId}`} id={`tab-${tabId}`} - className={`tab ${isActive ? 'active' : ''}`} + className={`tab ${isActive ? "active" : ""}`} onClick={() => !disabled && setActiveTab(tabId)} disabled={disabled} type="button" @@ -5317,8 +5405,8 @@ interface TabPanelProps { function TabPanel({ tabId, children }: TabPanelProps) { const context = useContext(TabsContext); - if (!context) throw new Error('TabPanel must be used within Tabs'); - + if (!context) throw new Error("TabPanel must be used within Tabs"); + const { activeTab } = context; const isActive = activeTab === tabId; @@ -5327,7 +5415,7 @@ function TabPanel({ tabId, children }: TabPanelProps) { role="tabpanel" id={`panel-${tabId}`} aria-labelledby={`tab-${tabId}`} - className={`tab-panel ${isActive ? 'active' : ''}`} + className={`tab-panel ${isActive ? "active" : ""}`} hidden={!isActive} > {isActive ? children : null} @@ -5350,20 +5438,22 @@ function TabExample() { Profile Settings - Billing + + Billing + - +

    Profile Content

    Manage your profile information here.

    - +

    Settings Content

    Adjust your application settings.

    - +

    Billing Content

    Manage your billing information.

    @@ -5377,6 +5467,7 @@ export default TabsCompound; ``` **Benefits of Compound Components:** + - **Flexible API**: Users can compose components however they need - **Separation of Concerns**: Each component has a single responsibility - **Implicit State Sharing**: Context eliminates prop drilling @@ -5417,11 +5508,11 @@ class MouseTracker extends React.Component { }; componentDidMount() { - window.addEventListener('mousemove', this.handleMouseMove); + window.addEventListener("mousemove", this.handleMouseMove); } componentWillUnmount() { - window.removeEventListener('mousemove', this.handleMouseMove); + window.removeEventListener("mousemove", this.handleMouseMove); } render() { @@ -5442,17 +5533,19 @@ function App() { {({ x, y }) => (

    Mouse Position

    -

    X: {x}, Y: {y}

    +

    + X: {x}, Y: {y} +

    @@ -5495,7 +5588,7 @@ class DataFetcher extends React.Component< fetchData = async () => { this.setState({ loading: true, error: null }); - + try { const response = await fetch(this.props.url); if (!response.ok) { @@ -5504,9 +5597,9 @@ class DataFetcher extends React.Component< const data = await response.json(); this.setState({ data, loading: false }); } catch (error) { - this.setState({ - error: error instanceof Error ? error : new Error('Unknown error'), - loading: false + this.setState({ + error: error instanceof Error ? error : new Error("Unknown error"), + loading: false, }); } }; @@ -5548,7 +5641,10 @@ function UserProfile({ userId }: { userId: number }) { ```tsx // Custom hook version of mouse tracker function useMousePosition(): MousePosition { - const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + const [mousePosition, setMousePosition] = useState({ + x: 0, + y: 0, + }); useEffect(() => { const handleMouseMove = (event: MouseEvent) => { @@ -5558,8 +5654,8 @@ function useMousePosition(): MousePosition { }); }; - window.addEventListener('mousemove', handleMouseMove); - return () => window.removeEventListener('mousemove', handleMouseMove); + window.addEventListener("mousemove", handleMouseMove); + return () => window.removeEventListener("mousemove", handleMouseMove); }, []); return mousePosition; @@ -5574,8 +5670,8 @@ function useApi(url: string): DataFetcherState & { refetch: () => void } { }); const fetchData = useCallback(async () => { - setState(prev => ({ ...prev, loading: true, error: null })); - + setState((prev) => ({ ...prev, loading: true, error: null })); + try { const response = await fetch(url); if (!response.ok) { @@ -5584,10 +5680,10 @@ function useApi(url: string): DataFetcherState & { refetch: () => void } { const data = await response.json(); setState({ data, loading: false, error: null }); } catch (error) { - setState({ + setState({ data: null, loading: false, - error: error instanceof Error ? error : new Error('Unknown error') + error: error instanceof Error ? error : new Error("Unknown error"), }); } }, [url]); @@ -5606,17 +5702,19 @@ function App() { return (

    Mouse Position

    -

    X: {mousePosition.x}, Y: {mousePosition.y}

    +

    + X: {mousePosition.x}, Y: {mousePosition.y} +

    @@ -5624,15 +5722,21 @@ function App() { } function UserProfile({ userId }: { userId: number }) { - const { data: user, loading, error, refetch } = useApi(`/api/users/${userId}`); + const { + data: user, + loading, + error, + refetch, + } = useApi(`/api/users/${userId}`); if (loading) return
    Loading user...
    ; - if (error) return ( -
    - Error: {error.message} - -
    - ); + if (error) + return ( +
    + Error: {error.message} + +
    + ); if (!user) return
    User not found
    ; return ( @@ -5693,7 +5797,7 @@ function Form>({ const handleChange = useCallback((field: keyof T) => (value: T[keyof T]) => { setValues(prev => ({ ...prev, [field]: value })); - + if (touched[field]) { const error = validateField(field, value); setErrors(prev => ({ ...prev, [field]: error })); @@ -5706,7 +5810,7 @@ function Form>({ setErrors(prev => ({ ...prev, [field]: error })); }, [values, validateField]); - const handleSubmit = useCallback((onSubmit: (values: T) => void) => + const handleSubmit = useCallback((onSubmit: (values: T) => void) => async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); @@ -5827,17 +5931,18 @@ function LoginPage() { **Render Props vs Hooks Comparison:** -| Feature | Render Props | Custom Hooks | -|---------|--------------|--------------| -| **Syntax** | More verbose | Cleaner, simpler | -| **Reusability** | Good | Excellent | -| **Composition** | Nested functions | Linear composition | -| **TypeScript Support** | Complex generics | Better inference | -| **Performance** | Can cause extra renders | More optimized | -| **Learning Curve** | Steeper | Easier | -| **Modern React** | Legacy pattern | Recommended approach | +| Feature | Render Props | Custom Hooks | +| ---------------------- | ----------------------- | -------------------- | +| **Syntax** | More verbose | Cleaner, simpler | +| **Reusability** | Good | Excellent | +| **Composition** | Nested functions | Linear composition | +| **TypeScript Support** | Complex generics | Better inference | +| **Performance** | Can cause extra renders | More optimized | +| **Learning Curve** | Steeper | Easier | +| **Modern React** | Legacy pattern | Recommended approach | **When to Use Each:** + - **Render Props**: When you need very flexible component composition or working with class components - **Custom Hooks**: For most modern React applications, simpler logic sharing, and better performance