Pustakam Library

Free Programming learning guide

Learn React JS: Build Web Apps from Scratch

Learn React JS: Build Web Apps from Scratch a free intermediate-level guide covering learn react js build web apps from scratch. Learn with clear...

121 min read14 chaptersintermediate

What you will learn

  1. Introduction to React and Modern JavaScript
  2. React Fundamentals: Components and JSX
  3. State Management in React: useState and useEffect
  4. Component Composition and Reusability
  5. Handling Forms and User Input
  6. React Router: Client-Side Navigation
  7. State Management with Context API
  8. Interacting with APIs: Fetching and Managing Data
  9. Performance Optimization in React
  10. Advanced State Management with Redux Toolkit
  11. Authentication and Authorization in React
  12. Testing React Applications
  13. Styling in React: CSS-in-JS and Component Libraries
  14. Deployment and CI/CD for React Apps

1. Introduction to React and Modern JavaScript

Modern JavaScript Essentials for React Development JavaScript has evolved dramatically since the early days of jQuery, and React thrives on the modern features that make code more declarative, maintainable, and expressive. If you've been writing JavaScript for a while, you're likely familiar with var, loops, and callbacks. But today’s React apps demand mastery of ES6+ features that streamline component logic, data handling, and modular architecture. Let’s cut through the buzzwords and focus on what actually matters when building real React applications—features like arrow functions, destructuring, modules, and classes, all of which become second nature once you see them in action. --- Why ES6+ Matters in React React was designed with modern JavaScript in mind. JSX—React’s syntax for defining UI components—is not HTML, but a JavaScript extension that relies on expressions, functions, and object literals. Without strong ES6+ fundamentals, React code becomes verbose, error-prone, and hard to scale. Consider a simple counter component written in pre-ES6 JavaScript: Now compare it with a modern ES6+ version: The difference isn’t just syntax—it’s clarity, immutability, and functional style. Modern JavaScript enables React’s component model to shine. --- Core ES6+ Features You Need Right Now Let’s focus on the ES6+ features that directly empower React development. These are not optional extras—they’re prerequisites. --- 1. Arrow Functions: Concise and Context-Aware Arrow functions simplify function syntax and lexically bind this, eliminating the need for .bind() in callbacks. In React, arrow functions are ideal for event handlers and inline callbacks: ⚠️ Caution: Avoid defining arrow functions inside render or return in performance-critical components. Instead, define them as class methods or outside the component. --- 2. Destructuring: Cleaner Code, Less Noise Destructuring lets you unpack values from arrays and objects into distinct variables. In React, destructuring is everywhere: This single line replaces the old this.state.count and this.setState() boilerplate. Destructure props in functional components: No more props.name or props.email—cleaner, more readable code. --- 3. Modules: Organize Code Like a Pro ES6 modules allow you to split code across files and import only what you need. In React: Modules reduce global scope pollution, improve reusability, and make testing easier. --- 4. Classes and this: When You Need OOP While React now favors functional components with hooks, some legacy or complex stateful components still use classes. Note the arrow function on increment—it ensures this refers to the component instance, not the button or window. ⚠️ Best Practice: Prefer functional components and hooks (useState, useEffect) unless you're working with legacy code or advanced lifecycle needs. --- 5. Template Literals: Dynamic Strings Made Easy Backticks () let you embed expressions inside strings: In React: Template literals are especially useful in error messages, logs, and dynamic class names: --- 6. Spread and Rest Operators: Handling …

2. React Fundamentals: Components and JSX

Components: The Building Blocks of React Applications Every React application is built from reusable components—like LEGO bricks that snap together to create complex interfaces. A single component might render a button, a form, or an entire page, but the magic lies in how these pieces connect. Without components, you’d be writing sprawling scripts that mix UI logic with business logic, making your app harder to maintain and scale. Consider a dashboard that displays user activity, weather data, and recent notifications. Without components, you’d have one giant function handling all these responsibilities. With components, you can break it into smaller pieces: a <WeatherCard, a <NotificationList, and a <UserActivityGraph. Each one focuses on a single responsibility, making your code easier to debug, test, and reuse. --- Understanding JSX: Writing HTML-Like Syntax Within JavaScript JSX (JavaScript XML) is a syntax extension that allows you to write HTML-like structures within JavaScript. It looks like HTML but compiles down to optimized JavaScript calls. While not required, JSX is a core part of the React developer experience and enables a declarative, readable way to define UI. Why JSX? - Familiar Syntax: Write UI markup alongside logic, using familiar HTML tags. - Compile-Time Safety: JSX is transformed into React.createElement() calls during build (via Babel), catching errors early. - Expressiveness: Embed JavaScript expressions directly using curly braces {}. 🔍 Best Practice: Use curly braces {} to embed dynamic values, expressions, or even function calls inside JSX. --- Creating React Components: From Functions to Classes React components come in two primary flavors: function components and class components. While both can manage state and lifecycle, modern React emphasizes function components with Hooks (introduced later), but understanding class components remains important for maintaining legacy code and grasping React’s evolution. Function Components: The Modern Standard Function components are JavaScript functions that return JSX. They’re concise, easier to test, and don’t require this binding. Key Features: - Accept props (properties) as their first argument. - Return JSX directly. - No this keyword—avoids confusion around lexical scoping and this binding. ⚠️ Caution: Avoid using this in function components unless you're working with legacy patterns or higher-order components (HOCs) that expect class-like behavior. --- Class Components: The Traditional Approach Class components extend React.Component and use this.state and lifecycle methods. They’re still valid but less common in new codebases. Key Features: - State: Can manage internal state via this.state. - Lifecycle Methods: componentDidMount, componentDidUpdate, componentWillUnmount, etc. - this Binding: Requires careful handling of this context (e.g., using arrow functions for methods). 📌 Best Practice: When using class components, bind event handlers or use arrow functions to ensure this refers to the component instance. ✅ Pro Tip: Many class components can be refactored into cleaner function components …

3. State Management in React: useState and useEffect

State in Functional Components: useState and useEffect Imagine you're building a dashboard that shows real-time stock prices. The numbers flicker and change every second, and your users expect the UI to reflect those changes instantly. How do you make that happen without constantly reloading the page? This is where state management in React becomes critical. Functional components—now the standard way to write React code—use hooks to manage state and side effects. Two hooks stand out as your primary tools: useState for local component state and useEffect for managing side effects like data fetching, subscriptions, and DOM updates. This chapter assumes you’ve written a few components and understand JSX and basic React rendering. We’ll move quickly through the essentials and focus on real-world patterns you’ll use every day. --- Managing Local State with useState React components are stateless by default. When you write a function that returns JSX, it renders the same output every time—unless you introduce state. The useState hook is your gateway to local state in functional components. How useState Works useState returns an array with two elements: 1. The current state value 2. A state setter function You call it with an initial value, and React keeps track of that value across re-renders. 🔁 Important: When you call setCount, React schedules a re-render. The component runs again, but useState remembers the last value of count—it doesn’t reset to 0. State Updates Are Asynchronous State updates don’t happen immediately. Instead, React batches them for performance. This means you can’t rely on the new state right after calling the setter: If you need to update state based on the previous value, pass a function to the setter: This ensures you’re working with the latest state, even if multiple updates are batched. Multiple State Variables You can (and should) use multiple useState calls for different pieces of state: This keeps state updates isolated and predictable. Avoid cramming multiple values into a single object unless they’re tightly related. State with Objects and Arrays State doesn’t have to be a primitive. You can store objects or arrays: ✅ Best Practice: Always create a new object or array when updating state, not mutate the existing one. Use the spread operator (...) to preserve unchanged properties. --- Side Effects and useEffect State isn’t just for storing data—it’s often used to trigger side effects: fetching data, setting up timers, subscribing to events, or manually updating the DOM. The useEffect hook lets you perform these operations after rendering. The Basics: Running an Effect Here, useEffect starts a timer when the component mounts. The empty dependency array ([]) means it runs only once, like componentDidMount. Dependency Arrays: When Does the Effect Run? The second argument to useEffect is …

4. Component Composition and Reusability

Why Your Components Should Feel Like Lego Bricks Imagine opening a box of Lego and finding 50 identical 2x4 bricks. With just those, you could build a skyscraper, a spaceship, or a dragon. Now imagine those bricks were all different shapes—some too big, some too small, some with weird protrusions. Building anything would be frustrating, and you’d constantly be gluing pieces together that weren’t meant to connect. Building UIs in React is similar. Without intentional design, your components become one-off shapes—each tailored to a single UI scenario. Over time, your codebase becomes a pile of mismatched pieces that are hard to reuse, hard to maintain, and hard to reason about. This chapter is about flipping that script. We’ll explore how to design components to be modular, compose them into flexible hierarchies, and create patterns that scale—so your app grows like a well-designed Lego set, not a tangled mess. We’ll cover: - Breaking UI into small, reusable pieces - Composing components to build complex UIs from simple parts - Advanced patterns like higher-order components and compound components - Organizing your codebase so components stay reusable and maintainable Let’s begin with the foundation: creating and composing components to avoid duplication. --- Crafting Reusable Components At the heart of React is the idea that UI is made of components, and components should be small, focused, and reusable. But reusability doesn’t happen by accident—it’s a design decision. The Single Responsibility Principle for Components Just as a function should do one thing well, a component should represent one concept in your UI. Consider a button: This button encapsulates: - Visual appearance (button) - Behavior (onClick) - State (disabled) It’s reusable because it doesn’t care where it’s used—just how it’s used. ✅ Best Practice: Name components after what they represent, not where they live or how they’re used. UserAvatar is better than SidebarAvatar. --- Avoid Prop Drilling with Composition A common mistake is passing props through multiple layers: This is prop drilling—passing props down through intermediary components that don’t need them. It makes components brittle and hard to reuse. Solution: Use composition—pass components as children or props. Here, Sidebar and UserProfile don’t need to know about isDarkMode—they just render their children. This decouples behavior from structure. 🔁 Pattern: Use children prop or component props to inject behavior where needed, not where it’s defined. --- Real-World Example: A Reusable Modal Let’s build a modal that can be reused across your app. Now, anywhere in your app: ✅ Key Insight: The Modal component doesn’t care what’s inside—it just manages visibility, focus, and escape behavior. This is reusability through abstraction. --- Composing Components for Complex UIs Reusable components are powerful, but real UIs are often composed of multiple interacting …

5. Handling Forms and User Input

Forms in React: Making Data Submission Predictable Imagine a user trying to sign up for your app, only to be met with a form that resets every time they mistype their password. Or a login form that doesn’t tell them why their credentials were rejected. These aren’t just minor UX annoyances—they’re failure points that break trust and discourage engagement. In React, forms aren’t just HTML—they’re living, stateful components that respond to user input in real time. This chapter shows you how to build forms that feel intuitive, validate before submission, and handle errors gracefully—without losing the user’s progress or overwhelming them with technical jargon. --- Controlled Forms: Connecting UI to State The key idea behind controlled forms is simple: make React the single source of truth for form data. Instead of letting the browser manage input values, you store them in component state and update them in response to user events. Here’s why this matters: - Predictability: Every keystroke updates the state, making debugging and testing straightforward. - Validation: You can validate input as the user types (or after submission) using the same state object. - Reset and Clear: You can reset the form to its initial state by resetting the state object. - Unified Behavior: All inputs follow the same pattern, reducing code duplication. The Controlled Input Pattern Every controlled input follows this template: This pattern applies to input, textarea, and select elements. Notice: - value is bound to the state, not the DOM. - onChange updates the state with the new value. - The input is always in sync with React state. 💡 Best Practice: Use onChange for all text inputs. For checkboxes and radio buttons, use onChange with event.target.checked. For select dropdowns, use onChange with event.target.value. --- Building a Multi-Field Form: Signup Example Let’s build a realistic signup form with first name, email, and password fields. We’ll use a single state object and destructuring for cleaner code. ✅ Key Features Used: - useState for local form state - Spread operator (...prev) to preserve other fields - Dynamic field name binding via [name]: value - onChange event to update state - onSubmit event with e.preventDefault() This pattern scales easily. Need a new field? Just add it to the state object and include a matching input. --- Validation: Stop Invalid Data Before It Reaches Your Database Validation isn’t optional—it’s a gatekeeper between user intent and system integrity. But in React, you can do more than just check "is this valid?" You can guide the user as they type. Two-Phase Validation Strategy 1. Inline Validation: Validate as the user types (e.g., check email format). 2. On-Submit Validation: Validate the entire form when submitted. This keeps UX snappy and prevents frustration. …

6. React Router: Client-Side Navigation

Why Routing Matters in Single-Page Applications Picture this: You’ve built a React application with multiple pages—maybe a homepage, a product catalog, a user dashboard, and an about page. Without client-side routing, every navigation would force a full page reload, wiping out your app's state and forcing users to wait for the entire application to reload. That defeats the purpose of building a smooth, responsive single-page application (SPA). React Router bridges this gap by enabling client-side navigation, allowing users to move between views without reloading the page. It turns your React app into a multi-page experience that feels instantaneous. Whether you're building a blog, an e-commerce site, or a complex dashboard, React Router provides the tools to manage navigation seamlessly. By the end of this chapter, you’ll be able to: - Set up React Router in a new or existing React app - Create clean, maintainable route structures using nested routes - Extract dynamic segments from URLs using route parameters - Navigate programmatically (e.g., after form submission) - Protect routes with authentication logic - Handle query strings and unexpected routes with a 404 page Let’s begin by installing and configuring React Router in a practical scenario. --- Setting Up React Router Installing React Router React Router is a separate library maintained by Remix. For most React applications, use react-router-dom, which includes support for web applications (as opposed to react-router-native for mobile apps). 💡 Note: Always use React Router v6, which introduced a simpler API with <Routes and <Route components replacing the older Switch and Route pattern. Basic App Setup Create a new React app or use an existing one. Ensure you have a clean structure. For example: In index.js, wrap your <App with <BrowserRouter: ✅ Best Practice: <BrowserRouter uses the HTML5 history API to keep your UI in sync with the URL. It's the standard for web apps. --- Defining Routes with <Routes and <Route React Router v6 simplifies routing with a declarative approach using <Routes and nested <Route components. This structure is clean, readable, and easy to scale. Example: Basic Page Routing - Each <Route maps a URL path to a component. - The <Routes component ensures only one route renders at a time. - The element prop accepts a React component (not a function that returns JSX). ⚠️ Caution: In v6, component and render props are deprecated. Always use element={<Component /}. Adding a Navigation Bar Create a reusable Navbar component: - Use <Link instead of <a to prevent full page reloads. - This keeps navigation fast and preserves app state. --- Dynamic Routing with Route Parameters Many pages rely on dynamic data—like a product page that shows details based on an ID. React Router supports route parameters via URL …

7. State Management with Context API

Why Context API Beats Prop Drilling for Global State Imagine a React app with a deeply nested component tree where every intermediate component must pass down state via props just to reach a deeply buried child. This is prop drilling—where props cascade through layers that don’t need them, creating tight coupling and fragile code. Context API breaks this pattern by letting you define global state once and consume it anywhere, without manual prop passing. Consider a shopping cart in an e-commerce app. The cart state must be accessible from the product listing page, the header, the checkout modal, and even a sidebar cart preview. Without Context, you’d need to lift state to a common ancestor like <App and thread it through every component in between—even <Header doesn’t care about cart items, yet it receives them anyway. Context lets you publish the cart once, and any component can subscribe directly, cleanly decoupling state from presentation. In this chapter, you’ll move beyond prop drilling by creating and consuming context, optimizing performance with memoization, and pairing context with useReducer for predictable state logic. You’ll build examples that mirror real-world use cases—like theme switching, user authentication, and shopping carts—without reaching for external libraries. --- Creating and Providing Context React’s Context API ships with two core APIs: createContext and the Provider component. Together, they let you define a “global variable” that components can read without prop drilling. Step 1: Create a Context This line creates a context object with two properties: - CartContext.Provider - CartContext.Consumer You’ll use the Provider to share state; components use the Consumer (or the useContext hook) to read it. 💡 Best Practice: Prefix context names with what they provide—e.g., CartContext, ThemeContext. This makes it clear what kind of data the context carries. Step 2: Provide the Context at a High Level Wrap your app (or a subtree) with the Provider and pass the current state as the value prop: Here, value is an object containing both the cart state and the setter. Any component inside <App can access these via context. ⚠️ Caution: Avoid passing non-primitive values like objects or functions directly in value unless they're stable. Recreating these on every render causes unnecessary consumer re-renders. --- Consuming Context in Nested Components Now let’s consume the context in deeply nested components—like a <CartPreview inside the <Header—without prop drilling. Option 1: Using useContext Hook This hook reads the nearest context value from the component tree above. It’s clean, concise, and avoids the render-props pattern of the older Consumer component. ✅ Best Practice: Use useContext instead of the <CartContext.Consumer render-prop pattern. It’s more readable and integrates better with other hooks. Option 2: Using Context.Consumer (Legacy Pattern) For completeness, here’s the older pattern: While …

8. Interacting with APIs: Fetching and Managing Data

From UI Mockups to Live Data: The API Gap You Can’t Ignore Imagine shipping a feature-complete React dashboard that lets users filter, sort, and paginate a list of products—only to realize the data isn’t real. The product names are still labeled “Product 1,” “Product 2,” and the prices are all set to $9.99. Without a real API, your polished UI is just a static mock. Bridging that gap requires not just fetching data, but managing it responsibly: handling loading states, recovering from failures, caching responses to avoid redundant calls, and even updating the UI optimistically before the server confirms the change. In this chapter, you’ll learn how to make your React apps truly dynamic by interacting with real-world APIs—both RESTful and GraphQL—using fetch and axios. You’ll go beyond basic requests and implement robust patterns with useEffect, custom hooks, and state management to handle loading, errors, and caching. By the end, you’ll be able to build responsive, resilient data-driven interfaces that feel fast and reliable, even when the network isn’t. --- Fetching Data with fetch and axios The Two Main Tools: fetch vs. axios Both fetch and axios allow you to send HTTP requests, but they differ in syntax and behavior. | Feature | fetch | axios | |--------|--------|--------| | Built into browsers? | Yes | No (requires install) | | Response parsing | Manual (response.json()) | Automatic (returns JSON by default) | | Error handling | Only on network failure (not HTTP errors like 404) | Catches both network and HTTP errors | | Request/response interceptors | No | Yes | | Canceling requests | Requires AbortController | Built-in CancelToken (legacy) or AbortController | | Transformers | No | Yes (modify request/response data) | While fetch is native and lightweight, axios offers a more developer-friendly API and better error handling out of the box—making it a popular choice in React applications. --- Making a fetch Request with useEffect Let’s build a component that fetches a list of users from a REST API. 🔁 Best Practice: Always clean up async operations in useEffect to prevent memory leaks and duplicate requests. Use AbortController for fetch, or axios.CancelToken (legacy) / AbortController with axios. --- Using axios for Simplicity With axios, the same logic becomes cleaner: 📦 Install axios: 🔍 Note: Modern axios supports AbortController directly: --- Handling Loading, Error, and Success States Building robust UIs means respecting the user’s time and attention. Never let your app freeze or show broken interfaces. State Machine Pattern Think of your data-fetching flow as a state machine: - Initial: No data, not loading - Loading: Fetching data - Success: Data arrived - Error: Something went wrong Then render based on status: ✅ This pattern scales well and …

9. Performance Optimization in React

Why Your React App Feels Slow (And How to Fix It) Every React developer has felt the frustration: the app works fine in development, but in production it crawls. A feature that seemed simple—like a list of 500 items or a modal that re-renders on scroll—suddenly turns into a sluggish mess. Users don’t notice your clean component structure or elegant state management. They notice when the UI stutters, when typing lags, or when a button takes half a second to respond. This chapter isn’t about writing faster code—it’s about writing less code that runs less often. We’ll focus on the tools and patterns that prevent unnecessary work, reduce bundle size, and keep your app snappy even under real-world load. --- 1. Breaking the Bundle: Code Splitting with React.lazy and Suspense Imagine launching a brand-new React app in production. Every line of code—your components, libraries, even nodemodules—gets bundled into a single JavaScript file. On a fast connection, this might load in 500ms. On a 3G connection? Try five seconds. And if your app is 500KB+ (which is common), users are staring at a blank screen while the browser parses and executes all that code. The solution: split your bundle. Instead of shipping everything at once, load only what’s needed for the current route or interaction. 1.1. How Code Splitting Works React provides two built-in tools for this: - React.lazy(): A function that dynamically imports a component. - <Suspense: A component that shows fallback UI while the lazy component loads. Together, they let you defer loading heavy components until they’re actually needed. When the button is clicked, HeavyComponent is fetched only then. The Suspense wrapper ensures users aren’t staring at a blank screen—you can show a spinner, skeleton, or friendly message. 💡 Best Practice: Always wrap lazy-loaded components in Suspense, even if the fallback is subtle. Users notice sudden UI jumps more than loading indicators. 1.2. Real-World Example: Dashboard with Lazy Routes Let’s apply this to a real app—a dashboard with multiple routes. Instead of bundling all routes together: Now, when a user visits /reports, only the Reports component and its dependencies are downloaded. The initial bundle drops from, say, 400KB to 120KB. That’s a 70% reduction in startup time. ⚠️ Caution: Don’t over-split. Loading 20 tiny components on every route defeats the purpose. Use code splitting for heavier, rarely-used features (like admin panels, analytics dashboards, or modals). 1.3. Using Suspense with Router-Level Code Splitting Most modern React apps use react-router-dom. You can integrate lazy loading directly in your routes: Each route loads only when navigated to. This is especially powerful in apps where users rarely access certain sections (e.g., admin areas). 🔧 Pro Tip: Use import() syntax with dynamic imports for …

10. Advanced State Management with Redux Toolkit

Why Redux Toolkit is the Modern Standard for React State Management Scalable React applications often hit a ceiling with local state and Context API. As features multiply, prop drilling, re-renders, and shared state logic become maintenance nightmares. Enter Redux Toolkit—the official, batteries-included toolset from Redux’s creators that eliminates boilerplate while keeping state predictable. Unlike the verbose setup of traditional Redux, Toolkit streamlines everything from store configuration to async logic. It’s not just a helper library; it’s the recommended way to write Redux in 2024. And with RTK Query built in, you get data fetching without the sprawl of separate useEffect calls or middleware chains. This chapter bridges the gap between basic React state and enterprise-grade architecture. You’ll build a real task tracker with complex filtering, offline persistence, and type-safe APIs—no dummy todos here. --- Setting Up Redux Toolkit in a React Project Start with a fresh React app using the latest LTS version. Install Toolkit and its React bindings: Create your store file at src/store/store.js: Key points: - configureStore automatically sets up Redux DevTools, thunk middleware, and Immer. - The middleware array combines default middleware (serialization checks, thunk) with RTK Query’s cache lifetimes. - All slices and API services register their reducers under unique keys. Wrap your app with the Provider in main.jsx (or index.js): --- Creating Slices with Immer Under the Hood Slices encapsulate reducer logic and actions in a single file. Let’s build a task tracker slice at src/features/tasks/tasksSlice.js: Why this pattern? - Immer integration: Every reducer can mutate state directly—Toolkit converts it to immutable updates. - prepare callback: Encapsulates complex payload creation (e.g., generating IDs) outside the reducer. - Selector factory: Centralize derived state logic to avoid recalculating in components. Pro tip: Extract selectors into a dedicated selectors.js file if the slice grows beyond 50 lines. --- Async Logic with createAsyncThunk and Thunks Traditional Redux requires you to write action types, constants, and separate thunk creators. Toolkit collapses this into a single function. Example: Fetching tasks from a REST API Key mechanics: - createAsyncThunk generates three action types: pending, fulfilled, rejected. - extraReducers handles these lifecycle actions without polluting your reducer logic. - rejectWithValue lets you return structured error payloads for components to display. Use the thunk in a component: --- RTK Query: Declarative Data Fetching and Caching RTK Query eliminates the need to write separate fetch calls, loading states, and cache management. It’s a lightweight data fetching and caching layer built on top of Redux Toolkit. Define an API service Create src/features/api/apiSlice.js: Using the hooks in components RTK Query automatically: - Deduplicates identical requests. - Caches responses and updates them when mutations invalidate tags. - Generates optimistic updates for mutations if you choose. - Handles loading, …

11. Authentication and Authorization in React

The Hidden Hole in Your React App Imagine launching your carefully crafted React application. It handles user input beautifully, navigates between pages effortlessly, and manages complex state with Redux Toolkit. Then, on launch day, you discover someone created a fake user account, accessed admin panels, and started deleting your entire database—all because you skipped authentication. This isn't hypothetical. In 2023, security researchers found that 78% of React applications had basic authentication flaws that allowed unauthorized data access. The damage? Average breach costs topped $4.45 million. The problem isn't that developers skip authentication—they often implement it poorly. Storing tokens in localStorage, hardcoding roles, or skipping token refresh logic creates vulnerabilities that attackers exploit within hours. Let's fix this. Understanding Authentication vs. Authorization Before writing code, clarify these terms: Authentication verifies "who you are" (login credentials → JWT token) Authorization determines "what you can do" (token → permitted routes/actions) In React applications: - Authentication happens when users submit credentials (login form) - Authorization happens when the app checks tokens before rendering protected routes or performing actions This distinction matters. You might authenticate users via JWT but authorize them based on: - User role (admin vs. regular user) - Session duration (token expiration) - Two-factor requirements Setting Up the Authentication Flow 1. The Token Lifecycle A robust authentication flow involves: Each step requires careful handling. Let's implement this flow systematically. 2. Creating the Authentication Service Layer Start by abstracting API calls behind a dedicated service: Security Note: This example uses localStorage for simplicity. In production, consider HttpOnly cookies with secure flags. 3. Implementing Token Refresh Logic Tokens expire. Plan for silent reauthentication: Protecting Routes with Authentication 1. The ProtectedRoute Component Create a reusable component that checks authentication status: 2. Integrating with React Router Modify your router configuration: Role-Based Access Control in Practice 1. The Authorization Provider Pattern Create a context that makes roles available throughout your app: 2. Conditional UI Based on Roles Use the context to show/hide elements: 3. Role-Based API Requests Modify your API service to include the token: Advanced Patterns and Pitfalls 1. Secure Token Storage Strategies While localStorage is convenient, it's vulnerable to XSS attacks. Consider: Option A: HttpOnly Cookies (Recommended for production) - Server sets HttpOnly, Secure, SameSite=Strict cookies - Client never sees the token - Requires backend support Option B: Session Storage - Cleared when tab closes - Still vulnerable to XSS but less persistent Option C: Memory Storage (Short-lived apps) - Store token only in React state - Lost on page refresh - Requires auto-login 2. Token Expiration and Silent Refresh Implement a token refresh mechanism that: - Checks expiration before making requests - Silently refreshes when expired - Handles refresh token rotation securely 3. Error …

12. Testing React Applications

Why Testing Matters in React Development Imagine deploying a React app where a simple button click crashes the entire application because a prop wasn’t handled correctly. Or worse—your e-commerce checkout component silently fails to process payments because an API mock didn’t account for a new edge case. These aren’t hypotheticals; they’re real risks that testing helps mitigate. React’s declarative nature makes components predictable, but predictability doesn’t equal correctness. Without tests, even small changes can introduce regressions that ripple through your app, eroding user trust and increasing debugging time. This chapter shifts your focus from building React components to ensuring they work as intended. You’ll learn to set up a robust testing environment, write tests for components and hooks, mock external dependencies, and use snapshots to catch unintended changes. By the end, you’ll treat testing as a natural part of development—not an afterthought. --- Setting Up Jest and React Testing Library Before writing tests, you need the right tools. Jest handles test execution, assertions, and mocking, while React Testing Library (RTL) provides utilities to interact with components the way users do. Installing Dependencies Create a new React project (or use an existing one) and install the necessary packages: - @testing-library/react: Provides utilities to render components in a test environment. - @testing-library/jest-dom: Adds custom matchers like .toBeInTheDocument() for assertions. - @testing-library/user-event: Simulates user interactions more accurately than fireEvent. - jest: The test runner (included if you’re using Create React App, but install it explicitly for manual setups). Configuring Jest Jest works out of the box with Create React App, but for custom setups, create a jest.config.js file: - testEnvironment: jsdom simulates a browser environment for testing. - setupFilesAfterEnv: Points to a file that runs before tests (e.g., to extend expect with custom matchers). - moduleNameMapper: Mocks CSS imports (using identity-obj-proxy) to avoid errors. - transform: Uses Babel to transpile JavaScript/JSX. Extending Expect with Custom Matchers Create src/setupTests.js to add RTL’s DOM matchers: Now you can write assertions like: --- Writing Unit Tests for React Components Unit tests verify that individual components render correctly and behave as expected. Let’s start with a simple button component. Example: A Toggle Button Component Writing the Test Create ToggleButton.test.jsx: Key Points: 1. render: Mounts the component in a lightweight test environment. 2. screen: Provides queries to find elements (e.g., getByRole, getByText). 3. userEvent: Simulates realistic user interactions (e.g., clicks, typing). 4. Queries: Prefer semantic queries like getByRole over getByText for accessibility. --- Testing Props and Dynamic Content Components often rely on props. Test how they handle different prop values. Example: A User Greeting Component Test Cases Best Practices: - Test edge cases: What happens with empty strings, null, or undefined? - Avoid implementation details: Don’t test internal …

13. Styling in React: CSS-in-JS and Component Libraries

Why Traditional CSS Falls Short in React React components are self-contained units of functionality and UI. When you mix global stylesheets with component-specific styles, you quickly run into maintainability nightmares. Imagine a button component whose styles are scattered across App.css, Button.module.css, and an inline style prop—now scale that to dozens of components. CSS-in-JS and component libraries solve this by co-locating styles with components, enabling scoped styles, dynamic theming, and better developer experience. In this chapter, you’ll move beyond basic inline styles and CSS modules to advanced, scalable styling solutions that fit naturally into the React ecosystem. You’ll learn how to use CSS-in-JS libraries like styled-components and Emotion, build consistent themes with context, and integrate full-fledged component libraries such as Material-UI and Chakra UI. By the end, you’ll be able to build polished, responsive UIs that scale with your app. --- CSS-in-JS: Styling Components with JavaScript CSS-in-JS allows you to write styles in JavaScript, often directly within your components. This approach leverages the full power of JavaScript—dynamic logic, props, theming, and hot reloading—while keeping styles co-located with your component logic. Why Use CSS-in-JS? - Scoped Styles: Styles are automatically scoped to components, preventing style leakage. - Dynamic Styling: Change styles based on props, state, or context without switching files. - Theming Made Easy: Use a theme provider to share colors, spacing, and fonts across your app. - Better Developer Experience: Autocomplete, linting, and hot reloading in your editor. - No Naming Conflicts: No more .nav-bar-2 classes—styles are tied to components. --- styled-components: A Popular Choice styled-components is a mature, widely used CSS-in-JS library. It uses template literals and tagged template literals to define styles alongside components. Installation Basic Usage Here, the Button component is defined entirely in JavaScript. The primary prop dynamically changes the background and text color. The styles are scoped to the component, so no CSS class name collisions occur. 💡 Best Practice: Use props to create reusable styles. Avoid inline functions that cause re-renders—memoize or lift logic out when possible. --- Emotion: A Lightweight Alternative Emotion is another powerful CSS-in-JS library, known for its performance and flexibility. It supports both string styles and object styles, and integrates well with React. Installation Basic Usage Emotion also supports the css prop for one-off styles: --- Theming with ThemeProvider Theming allows you to define a consistent design system—colors, spacing, fonts, shadows—across your application. Both styled-components and Emotion support theming via the ThemeProvider. Step 1: Define Your Theme Step 2: Wrap Your App with ThemeProvider Step 3: Use Theme in Styled Components ⚠️ Caution: Always access the theme via props.theme or the theme argument in the style function. Avoid hardcoding values. --- Dynamic Styles Based on Props and State CSS-in-JS shines when …

14. Deployment and CI/CD for React Apps

Preparing Your React App for the Wild A React app running locally in development mode is like a musician rehearsing in a soundproof room—it sounds perfect, but the moment you open the door to the real world, everything changes. Static assets need to be optimized, environment-specific configurations must be injected, and the entire build pipeline must be automated to ensure every change reaches users without manual intervention. This chapter isn’t about making your app work in Chrome DevTools—it’s about making it work in the wild, reliably, securely, and fast. By the end of this chapter, you’ll have taken a production-ready React app from local development to global deployment, and set up a CI/CD pipeline that handles future updates automatically. You’ll learn how to: - Bundle and optimize your app for performance using tools like Vite and Webpack - Deploy statically to platforms like Vercel, Netlify, and GitHub Pages - Manage environment variables across build and runtime phases - Automate testing and deployment using GitHub Actions - Handle secrets and configuration securely in CI environments Let’s begin by transforming your local React project into a deployable artifact—one that’s optimized, configurable, and ready for the internet. --- Building for Production React apps are built using tools like Vite or Create React App (CRA), which bundle your components, styles, and logic into static files. But in development, these tools generate source maps, hot-reload code, and serve unminified bundles. In production, none of that overhead is needed—or desired. Optimizing the Build When you run npm run build (or vite build), your React app is transformed into highly optimized static assets: - Bundling: All JavaScript, CSS, and assets are combined into a few files - Minification: Code is stripped of whitespace and renamed for smaller file sizes - Tree-shaking: Unused code is removed during bundling - Chunking: Large apps are split into smaller bundles for faster loading - Asset hashing: Filenames include content hashes (e.g., main.a1b2c3.js) so browsers cache aggressively 🔍 Best Practice: Use Vite for modern React apps—it’s faster than CRA out of the box and supports ES modules natively. If you're using Create React App, you can still achieve similar results with react-scripts build. After running the build, inspect the dist (Vite) or build (CRA) folder. You’ll see: This is what gets served in production. --- Configuring Environment Variables React apps often rely on environment-specific settings: API endpoints, feature flags, analytics keys, and third-party service URLs. These must be injected at build time (static values) or runtime (dynamic values), depending on the platform. Build-Time vs Runtime Variables | Type | When Injected | Example Use Case | Platform Support | |------|----------------|------------------|------------------| | Build-time | During npm run build | API base URL, feature …

Continue learning