Zero to Hero in React: A Beginner's Roadmap

Zero to Hero in React: A Beginner's Roadmap

Build modern web applications using hooks, context API, and more, even if you're new to coding.

Table of contents

In every line of code, we draft a new possibility. React serves as the canvas on which we paint our ever-evolving ideas.

Hi, I’m Subham Jaiswal, and this is your one-stop guide to React! Whether you’re starting from scratch or brushing up on your skills, this blog covers everything—from basics like components and state to advanced concepts like Hooks, Redux, and Context API.

It’s concise, practical, and packed with examples to help you build modern, scalable applications with ease. Let’s dive straight into the world of React and unlock its full potential—one step at a time! 🚀


Section 1: Introduction to React & Key Concepts

1.1. What is React?

  • React is a JavaScript library for building user interfaces (UIs).

  • Created by Facebook (now Meta) around 2013.

  • It’s component-based, meaning you build small, reusable pieces called components.

Key Points:

  • Declarative Approach: You tell React what the UI should look like; React updates the DOM (Document Object Model).

  • Virtual DOM: React keeps a lightweight “virtual” copy of the actual browser DOM for quick updates.

  • Learn Once, Write Anywhere: You can use React for websites, mobile apps (with React Native), or desktop apps.

Why is this important for non-CS folks?
It means you don’t have to learn how to manually manipulate web pages (the DOM). React figures out how to update things for you.

1.2. Understanding the Virtual DOM

  • Without React, changing an item on the webpage could require you to manually update the DOM, which is slow.

  • With React, you change your state/props in a component, and React automatically updates only the affected part in the real DOM.


Section 2: Environment Setup (Node, NPM, and Tools)

2.1. Install Node.js

  • Node.js is a JavaScript runtime that lets you run JS outside the browser.

  • It comes with npm (Node Package Manager), which helps you install libraries.

Why is this important?
Because React’s official tool (Create React App), and many other tools require Node to run.


Section 3: Create React App & Project Structure

3.1. Using Create React App (CRA)

  • Create React App is a CLI tool that sets up a React project with all modern configurations (Webpack, Babel, etc.).

  • No need to manually configure anything—perfect for beginners.

  • We can use other build tools also. Ex- vite.

npx create-react-app my-react-app
cd my-react-app
npm start
---------------------------------------
npm create vite@latest

3.2. Project Structure

my-react-app
├─ node_modules/
├─ public/
├─ src/
│   ├─ App.jsx
│   ├─ App.css
│   ├─ main.jsx
│   └─ ...
├─ index.html
├─ package.json
└─ ...
  • index.html: The single HTML file that loads your React app.

  • src/main.jsx: JavaScript entry point; renders the main <App /> component.

  • App.js: Default main component in react app.


Section 4: JSX & Rendering

4.1. What is JSX?

  • JSX is a syntax extension that allows you to write HTML-CSS code in JavaScript.

  • Browsers don’t understand JSX directly; a tool called Babel translates it into plain JavaScript.

  • React-19 has it’s own compiler.

Example:

function Hello() {
  return <h1>Hello, World!</h1>;
}

This gets compiled to:

//In classic/manual react runtime
function Hello() {
  return /*#__PURE__*/React.createElement("h1", {
    name: "name"
  }, "Hello, World!");
}

//In automatic react runtime
import { jsx as _jsx } from "react/jsx-runtime";
function Hello() {
  return /*#__PURE__*/_jsx("h1", {
    name: "name",
    children: "Hello, World!"
  });
}

4.2. Rendering in the Browser

  • The root HTML element is usually a <div id="root"></div> inside index.html.

  • In older React versions (before 18):

      ReactDOM.render(<App />, document.getElementById('root'));
    
  • In React 18+:

      import ReactDOM from 'react-dom/client';
      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(<App />);
    

Section 5: Components (Functional & Class)

5.1. Functional Components

  • Functional Components are basically JavaScript functions that return JSX.

  • Advantages: Simpler, easier to read, and Hooks make them very powerful.

Example:

function Welcome() {
  return <h2>Welcome to my app!</h2>;
}

export default Welcome;

5.2. Class Components

  • Class Components are ES6 classes that extend React.Component.

  • Lifecycle methods (e.g., componentDidMount, render) are used here.

Example:

import React from 'react';

class Welcome extends React.Component {
  render() {
    return <h2>Welcome to my app!</h2>;
  }
}

export default Welcome;

Many modern React apps use functional components + Hooks. Class components are still valid but are often considered more “legacy” in new code.


Section 6: Props & State

6.1. Props (Properties)

  • Props are read-only data passed from a parent to a child component.

  • They’re like function parameters in JavaScript.

Example:

function Greet(props) {
  return <h3>Hello, {props.name}!</h3>;
}

// Usage:
<Greet name="Alice" /> //Hello, Alice
<Greet name="Bob" /> //Hello, Bob

6.2. State in Class Components

  • this.state is where you store data that can change over time.

  • Use this.setState() to update it.

Class Example:

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </>
    );
  }
}

6.3. State in Functional Components (useState)

  • With Hooks, you can manage state in function components using useState.
import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </>
  );
}

Section 7: Lifecycle Methods (Class Components)

7.1. The Lifecycle Stages

  • Mounting: When a component is inserted into the DOM (e.g., componentDidMount).

  • Updating: When props or state change (e.g., componentDidUpdate).

  • Unmounting: When a component is removed from the DOM (componentWillUnmount).

    Mounting: constructor() → render() → componentDidMount()
    Updating: render() → componentDidUpdate()
    Unmounting: componentWillUnmount()


Section 8: React Hooks (Functional Components)

1. useState

  • Purpose: Adds state to functional components.

  • Signature: const [state, setState] = useState(initialState);

  • Key Points:

    • useState returns an array with two elements:

      1. The current state value.

      2. A function to update the state.

    • Updating the state with setState triggers a re-render.

Example:

import React, { useState } from 'react';

function Counter() {
  // Initialize state variable 'count' to 0
  const [count, setCount] = useState(0);

  // Handler to increment count
  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };

  return (
    <div>
      <p>You clicked {count} times.</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default Counter;

2. useEffect

  • Purpose: Handles side effects in functional components (e.g., data fetching, subscriptions, DOM manipulations).

  • Signature: useEffect(() => { /* side effect */ }, [dependencies]);

  • Key Points:

    • By default, runs after every render (and re-render).

    • If you provide a dependency array (e.g. [count]), it only runs when those dependencies change.

    • Providing an empty dependency array ([]) means it runs only once (on mount).

Example (data fetching):

import React, { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(res => {
        if (!res.ok) throw new Error('Network response was not ok');
        return res.json();
      })
      .then(json => setData(json))
      .catch(err => setError(err.message));
  }, []);  // Run only once after the component mounts

  if (error) {
    return <p>Error: {error}</p>;
  }

  if (!data) {
    return <p>Loading...</p>;
  }

  return (
    <div>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default DataFetcher;

3. useContext

  • Purpose: Accesses a context value without prop drilling.

  • Signature: const value = useContext(MyContext);

  • Key Points:

    • useContext must be used inside a component that is wrapped in a Context.Provider.

    • You no longer have to pass the context down through each intermediate component.

Example:

import React, { createContext, useContext } from 'react';

// 1. Create the context
const ThemeContext = createContext('light');

function ChildComponent() {
  // 3. Access the context
  const theme = useContext(ThemeContext);
  return <div>The current theme is: {theme}</div>;
}

function ParentComponent() {
  // 2. Provide the context
  return (
    <ThemeContext.Provider value="dark">
      <ChildComponent />
    </ThemeContext.Provider>
  );
}

export default ParentComponent;

4. useReducer

  • Purpose: Manages complex state logic via a reducer function (similar to Redux pattern, but on a local component scale).

  • Signature: const [state, dispatch] = useReducer(reducer, initialState, init);

  • Key Points:

    • reducer is a function that takes (state, action) and returns a new state.

    • dispatch is used to trigger state changes by sending an action object.

Example:

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function CounterWithReducer() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </div>
  );
}

export default CounterWithReducer;

5. useRef

  • Purpose:

    1. Stores a mutable value that does not cause a re-render when updated.

    2. Directly references a DOM element (like document.getElementById in older React approaches).

  • Signature: const refContainer = useRef(initialValue);

  • Key Points:

    • refContainer.current is mutable.

    • Perfect for storing counters, timers, or references to DOM elements.

Example (focus an input element):

jsxCopy codeimport React, { useRef } from 'react';

function TextInputWithFocusButton() {
  const inputRef = useRef(null);

  const handleFocus = () => {
    // Access the DOM node directly
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>Focus the input</button>
    </div>
  );
}

export default TextInputWithFocusButton;

6. useCallback

  • Purpose: Returns a memoized version of a callback function, to avoid unnecessary re-creations on every render.

  • Signature: const memoizedCallback = useCallback(() => { /* function body */ }, [dependencies]);

  • Key Points:

    • Useful when passing callbacks to child components that may rely on reference equality to prevent re-renders.

    • The function will only change if one of the dependencies changes.

Example:

import React, { useState, useCallback } from 'react';

function Child({ onClick }) {
  console.log('Child is rendered');
  return <button onClick={onClick}>Click me</button>;
}

function Parent() {
  const [count, setCount] = useState(0);

  // Without useCallback, this handleClick would be re-created on every render
  const handleClick = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <Child onClick={handleClick} />
    </div>
  );
}

export default Parent;

7. useMemo

  • Purpose: Returns a memoized (cached) value. Helps optimize expensive calculations by recalculating only if dependencies change.

  • Signature: const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

  • Key Points:

    • Great for CPU-intensive calculations that shouldn’t run every time the component renders.

    • The function inside useMemo only runs when the specified dependencies change.

Example:

jsxCopy codeimport React, { useState, useMemo } from 'react';

function ExpensiveCalculationComponent() {
  const [number, setNumber] = useState(0);
  const [dark, setDark] = useState(false);

  // computeExpensiveValue is called only when 'number' changes
  const doubleNumber = useMemo(() => {
    console.log('Running expensive calculation...');
    return number * 2;
  }, [number]);

  // Toggle the theme
  const themeStyles = {
    backgroundColor: dark ? '#333' : '#FFF',
    color: dark ? '#FFF' : '#333',
  };

  return (
    <div style={themeStyles}>
      <input
        type="number"
        value={number}
        onChange={e => setNumber(parseInt(e.target.value, 10))}
      />
      <button onClick={() => setDark(prevDark => !prevDark)}>
        Toggle Theme
      </button>
      <p>Result: {doubleNumber}</p>
    </div>
  );
}

export default ExpensiveCalculationComponent;

8. useLayoutEffect

  • Purpose: Similar to useEffect, but fires synchronously after all DOM mutations. Useful for measuring layout and re-rendering before the browser paints.

  • Signature: useLayoutEffect(() => { /* side effect */ }, [dependencies]);

  • Key Points:

    • Blocking the browser’s paint can cause performance issues if overused.

    • Typically used for DOM measurements (e.g., reading layout, scroll position) and then synchronously updating layout before the browser re-renders.

Example:

jsxCopy codeimport React, { useState, useLayoutEffect, useRef } from 'react';

function LayoutEffectExample() {
  const [height, setHeight] = useState(0);
  const divRef = useRef(null);

  useLayoutEffect(() => {
    // This will run before the component is painted to the screen
    if (divRef.current) {
      setHeight(divRef.current.getBoundingClientRect().height);
    }
  }, []);

  return (
    <div>
      <div ref={divRef} style={{ border: '1px solid black', padding: '10px' }}>
        Measure my height!
      </div>
      <p>The above div's height is: {height}px</p>
    </div>
  );
}

export default LayoutEffectExample;

9. useDebugValue

  • Purpose: Shows a label or value in React DevTools for custom Hooks.

  • Signature: useDebugValue(value);

  • Key Points:

    • You typically call useDebugValue inside a custom hook to help with debugging.

    • Doesn’t affect your app’s behavior; it’s purely for development convenience.

Example (inside a custom hook):

import { useState, useEffect, useDebugValue } from 'react';

function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const updateStatus = () => setIsOnline(navigator.onLine);
    window.addEventListener('online', updateStatus);
    window.addEventListener('offline', updateStatus);

    return () => {
      window.removeEventListener('online', updateStatus);
      window.removeEventListener('offline', updateStatus);
    };
  }, []);

  // This will show "Online" or "Offline" in React DevTools
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

export default useOnlineStatus;

10. useImperativeHandle

  • Purpose: Customizes the instance value that’s exposed to parent components when using ref with forwardRef.

  • Signature: useImperativeHandle(ref, createHandle, [dependencies]);

  • Key Points:

    • Only use with forwardRef.

    • Allows you to limit or customize what gets exposed when a parent uses a ref on a child component.

Example (exposing a method in a child component):

import React, { useImperativeHandle, forwardRef, useRef } from 'react';

const ChildComponent = forwardRef((props, ref) => {
  const localInputRef = useRef();

  useImperativeHandle(ref, () => ({
    focusInput: () => {
      localInputRef.current.focus();
    }
  }));

  return (
    <input
      ref={localInputRef}
      type="text"
      placeholder="Child input"
    />
  );
});

export default ChildComponent;

// Usage in Parent

import React, { useRef } from 'react';
import ChildComponent from './ChildComponent';

function Parent() {
  const childRef = useRef();

  const handleFocus = () => {
    if (childRef.current) {
      childRef.current.focusInput();
    }
  };

  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={handleFocus}>Focus child input</button>
    </div>
  );
}

export default Parent;

11. useInsertionEffect (React 18+)

  • Purpose: A specialized Hook for injecting synchronous side effects (primarily CSS-in-JS libraries) before the browser paints.

  • Signature: useInsertionEffect(() => { /* side effect */ }, [dependencies]);

  • Key Points:

    • Runs before any DOM mutations are performed (unlike useLayoutEffect which runs after the DOM is updated).

    • Typically used for style insertion in CSS-in-JS libraries to avoid a flash of unstyled content.

Example (conceptual use, often done inside a library like styled-components):

import React, { useInsertionEffect } from 'react';

function InsertionEffectExample() {
  useInsertionEffect(() => {
    // Insert or update style tags in the document head
    const styleTag = document.createElement('style');
    styleTag.textContent = `
      .insertion-effect-example {
        color: blue;
        font-weight: bold;
      }
    `;
    document.head.appendChild(styleTag);

    return () => {
      // Cleanup if needed
      document.head.removeChild(styleTag);
    };
  }, []);

  return <div className="insertion-effect-example">Hello, styled by useInsertionEffect!</div>;
}

export default InsertionEffectExample;

12. useId (React 18+)

  • Purpose: Generates a unique, stable ID for server and client to help maintain consistency (useful for SSR or accessibility attributes).

  • Signature: const id = useId();

  • Key Points:

    • Avoids hydration mismatches between server and client.

    • Particularly helpful for linking form inputs and labels in SSR.

Example:

import React, { useId } from 'react';

function FormWithId() {
  const id = useId();

  return (
    <div>
      <label htmlFor={id}>Username: </label>
      <input id={id} type="text" />
    </div>
  );
}

export default FormWithId;

13. useTransition (React 18+)

  • Purpose: Lets you mark state updates as non-urgent (transitions), allowing the user interface to remain responsive.

  • Signature: const [startTransition, isPending] = useTransition();

  • Key Points:

    • startTransition wraps the state update that can be deferred.

    • isPending indicates if the transition is ongoing.

    • Useful for big rerenders (like massive lists) so the UI (e.g., typing) can remain snappy.

Example:

import React, { useState, useTransition } from 'react';

function SearchList({ items }) {
  const [query, setQuery] = useState('');
  const [filteredItems, setFilteredItems] = useState(items);

  const [startTransition, isPending] = useTransition();

  const handleChange = (e) => {
    const newQuery = e.target.value;
    setQuery(newQuery);

    startTransition(() => {
      const results = items.filter(item => item.includes(newQuery));
      setFilteredItems(results);
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} placeholder="Search" />
      {isPending && <p>Updating list...</p>}
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default SearchList;

14. useDeferredValue (React 18+)

  • Purpose: Defers re-rendering of non-urgent parts of your UI, letting higher-priority updates (like typing) remain responsive.

  • Signature: const deferredValue = useDeferredValue(value);

  • Key Points:

    • Typically used for large lists or content that can wait.

    • The deferredValue “lags behind” the real value, while value is kept in real time.

Example:

import React, { useState, useDeferredValue } from 'react';

function DeferredSearch({ items }) {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  // filteredItems will rely on the deferred value
  const filteredItems = items.filter(item => item.includes(deferredQuery));

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Type here"
      />
      <ul>
        {filteredItems.map((item, idx) => <li key={idx}>{item}</li>)}
      </ul>
    </div>
  );
}

export default DeferredSearch;

15. useSyncExternalStore (React 18+)

  • Purpose: A low-level Hook for subscribing to external data sources (like Redux or other store libraries) with a consistent behavior in concurrent rendering.

  • Signature: useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

  • Key Points:

    • Replaces useEffect for external store subscriptions.

    • Ensures that your components always see a consistent snapshot.

Example (conceptual, manually subscribing to window size):

import React, { useSyncExternalStore } from 'react';

// 1. Set up a store-like subscription
function subscribe(callback) {
  window.addEventListener('resize', callback);
  return () => window.removeEventListener('resize', callback);
}

function getSnapshot() {
  return {
    width: window.innerWidth,
    height: window.innerHeight
  };
}

function WindowSize() {
  // 2. Use the new useSyncExternalStore
  const size = useSyncExternalStore(subscribe, getSnapshot);

  return (
    <div>
      <p>Width: {size.width}</p>
      <p>Height: {size.height}</p>
    </div>
  );
}

export default WindowSize;

Custom Hooks

  • You can build your own Hooks to reuse logic across multiple components.

  • Rules:

    1. The name must start with use (useLocalStorage, useAuth, etc.).

    2. You can only call Hooks at the top level (not in loops or conditions).

Example: useLocalStorage

import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const storedValue = localStorage.getItem(key);
    return storedValue !== null ? JSON.parse(storedValue) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

export default useLocalStorage;

// Usage in any component

import React from 'react';
import useLocalStorage from './useLocalStorage';

function Profile() {
  const [name, setName] = useLocalStorage('name', '');

  return (
    <div>
      <p>Hello, {name}</p>
      <input value={name} onChange={e => setName(e.target.value)} />
    </div>
  );
}

export default Profile;

Understand Hooks at a Glance

  1. Basic Hooks:

    • useState – For local component state.

    • useEffect – For side effects (data fetching, DOM updates).

    • useContext – For context consumption without prop drilling.

    • useReducer – For more complex state updates using a reducer pattern.

    • useRef – For storing a mutable reference or accessing DOM nodes.

    • useCallback – For memoizing callback functions.

    • useMemo – For memoizing expensive calculations.

  2. Additional Hooks:

    • useLayoutEffect – Like useEffect, but fires synchronously after DOM changes.

    • useDebugValue – For dev tools debugging inside custom hooks.

    • useImperativeHandle – Customizing ref handling with forwardRef.

    • useInsertionEffect – For injecting critical side effects before the browser paints.

    • useId – Unique ID generation for SSR/client matching.

    • useTransition – For marking state updates as non-urgent (improving user experience).

    • useDeferredValue – For deferring re-renders of non-critical updates.

    • useSyncExternalStore – A stable way to subscribe to external stores consistently.

Knowing how and when to use these Hooks is key to building performant, maintainable React applications.


Section 9: Handling Events & Forms

9.1. Event Handling

  • React events use camelCase (e.g., onClick, onChange) instead of HTML style (onclick, onchange).

  • You pass a function reference, not a string.

Example:

<button onClick={handleClick}>Click me</button>

function handleClick() {
  alert('Button clicked!');
}

9.2. Forms (Controlled & Uncontrolled)

Controlled Components:

  • The form field’s value is controlled by React state.
function MyForm() {
  const [name, setName] = useState("");

  return (
    <input 
      type="text"
      value={name}
      onChange={e => setName(e.target.value)}
    />
  );
}

Uncontrolled Components:

  • Use refs to access the field’s value (DOM handles the state internally).
import React, { useRef } from "react";

function UncontrolledForm() {
  const inputRef = useRef();

  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`Submitted name: ${inputRef.current.value}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" ref={inputRef} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

export default UncontrolledForm;

Section 10: React Router

10.1. Why Routing?

  • A Single-Page Application (SPA) typically has one HTML page.

  • React Router simulates multiple pages using the browser’s history API.

10.2. Installing & Basic Setup

npm install react-router-dom

In your App.jsx (or similar root component):

import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './Home';
import About from './About';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}
  • <BrowserRouter>: The wrapper for your entire app.

  • <Routes>: Container for individual <Route> elements.

  • <Route path="/about" element={<About />} />: Renders <About /> when URL is /about.

10.3. Navigation

  • Link components replace normal anchors.
<Link to="/about">Go to About Page</Link>
  • useNavigate() for programmatic navigation.

  • you can use the replace option to replace the current URL in the history stack instead of adding a new entry

import React from "react";
import { useNavigate } from "react-router-dom";

export default function SomeComponent() {
  const navigate = useNavigate(); // Get the navigate function

  const handleClick = () => {
    navigate('/other-page', { replace: true });
  };

  return (
// Go back one step in history
    <button onClick={() => {navigate(-1)}}> Go Back </button>
    <button onClick={handleClick}>Go to other page</button>
  );
}

Section 11: Making API Calls & Data Fetching

11.1. Fetch or Axios?

  • fetch: Built into modern browsers (no installation required).

  • axios: A popular library with additional features that simplify HTTP requests, such as automatic JSON parsing, request cancellation, and interceptors.

fetch:

import React, { useState, useEffect } from "react";

function FetchExample() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then((response) => {return response.json()})
      .then((data) => setPosts(data))
      .catch((err) => setError(err))
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
}

axios:

import React, { useState, useEffect } from "react";
import axios from "axios";

function AxiosExample() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/posts")
      .then((response) => setPosts(response.data))
      .catch((err) => setError(err))
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
}

Section 12: Context API

12.1. Avoiding Prop Drilling

  • If you have to pass the same data through multiple components, it’s messy.

  • Context allows you to provide data at a higher level, and any nested component can consume it directly.

12.2. Creating & Using a Context

// 1. Create
const ThemeContext = React.createContext("light");

// 2. Provide
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 3. Consume
function Toolbar() {
  const theme = useContext(ThemeContext);
  return <div style={{ background: theme === "dark" ? "#000" : "#fff" }} />;
}

Section 13: Redux Basics

13.1. Why Redux?

  • Redux is a predictable state container for complex React apps.

  • One centralized store holds your entire application state.

  • Components dispatch actions to reducers to update the store.

ConceptDescription
StoreHolds the entire state of your app.
ActionA plain JavaScript object describing a change, e.g., { type: "INCREMENT" }.
ReducerA function taking (state, action) and returning a new state object.
DispatchThe method used to send actions to the store.

13.2. Setting Up Redux

npm install redux react-redux

Store:

// store.js
import { createStore } from 'redux';

const initialState = { count: 0 };

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    default:
      return state;
  }
}

const store = createStore(counterReducer);
export default store;

Providing the store:

import { Provider } from 'react-redux';
import store from './store';

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

Section 14: Advanced Redux & Middleware

14.1. react-redux Hooks

  • useSelector: Access store state.

  • useDispatch: Dispatch actions.

Example:

function Counter() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
    </>
  );
}

14.2. Async Actions (Redux Thunk)

  • For async tasks (e.g., fetching from an API), we use thunk or saga.

Thunk Setup:

npm install redux-thunk
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const store = createStore(reducer, applyMiddleware(thunk));

Async Action:

Step 1: Create Actions

export const fetchPostsRequest = () => ({ type: "FETCH_POSTS_REQUEST" });
export const fetchPostsSuccess = (posts) => ({ type: "FETCH_POSTS_SUCCESS", payload: posts });
export const fetchPostsFailure = (error) => ({ type: "FETCH_POSTS_FAILURE", payload: error });

Step 2: Create Thunk Action

A thunk action is a function that performs async operations and dispatches other actions.

export const fetchPosts = () => {
  return async (dispatch) => {
    dispatch(fetchPostsRequest()); // Start loading
    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts");
      const data = await response.json();
      dispatch(fetchPostsSuccess(data)); // Dispatch success action
    } catch (error) {
      dispatch(fetchPostsFailure(error.message)); // Dispatch failure action
    }
  };
};

Step 3: Update Reducer

Handle different states (loading, success, and failure) in the reducer.

const initialState = {
  loading: false,
  posts: [],
  error: null,
};

const postsReducer = (state = initialState, action) => {
  switch (action.type) {
    case "FETCH_POSTS_REQUEST":
      return { ...state, loading: true, error: null };
    case "FETCH_POSTS_SUCCESS":
      return { ...state, loading: false, posts: action.payload };
    case "FETCH_POSTS_FAILURE":
      return { ...state, loading: false, error: action.payload };
    default:
      return state;
  }
};

export default postsReducer;

Step 4: Dispatch Thunk Action in Component

Use useDispatch from React-Redux to dispatch the thunk action.

import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchPosts } from "./actions";

function Posts() {
  const dispatch = useDispatch();
  const { posts, loading, error } = useSelector((state) => state.posts);

  useEffect(() => {
    dispatch(fetchPosts()); // Dispatch async action
  }, [dispatch]);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

export default Posts;

Flow

  1. Component dispatches fetchPosts() (Thunk action).

  2. Redux Thunk middleware intercepts and executes fetchPosts().

  3. The thunk:

    • Dispatches FETCH_POSTS_REQUEST → Reducer sets loading: true.

    • Makes an API call.

    • Dispatches FETCH_POSTS_SUCCESS on success → Reducer updates posts with data.

    • Dispatches FETCH_POSTS_FAILURE on error → Reducer sets error.

Without Thunk:

  • Without thunk, you cannot perform side effects (like API calls) directly inside action creators.

  • You'll be limited to synchronous logic, such as updating the state immediately.

  • You’d need to move the async logic out of Redux, making it harder to manage, makes the code more verbose and less maintainable.


Section 15: Performance Optimization

15.1. React.memo and PureComponent

  • React.memo: Avoids unnecessary re-renders of functional components by performing a shallow comparison of props.

      const MyComponent = React.memo(function MyComponent({ data }) {
        return <div>{data}</div>;
      });
    
  • PureComponent: For class components, avoids re-renders by performing a shallow comparison of props and state.

15.2. useMemo and useCallback

  • useMemo: Memoize an expensive calculation so it’s not re-run on every render.

      function ExpensiveCalculation({ num }) {
        const result = useMemo(() => {
          // some CPU-intensive task
          return heavyCompute(num);
        }, [num]);
    
        return <div>{result}</div>;
      }
    

useCallback: Memoize a function to keep the same reference between renders.

// The Todos component is memoized using React.memo to avoid unnecessary re-renders
const Todos = memo(({ todos, addTodo }) => {
  console.log("child render"); // Logs whenever the Todos component re-renders

  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>; // Render the list of todos
      })}
      <button onClick={addTodo}>Add Todo</button> {/* Button to add a new todo */}
    </>
  );
});

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]); 

  const increment = () => {
    setCount((c) => c + 1);
  };

  // Problem: Without useCallback, this addTodo function would be re-created on every render,
  // causing the Todos component to re-render even if its props (todos and addTodo) didn't change.
  const addTodo = useCallback(() => {
    setTodos((t) => [...t, "New Todo"]); // Add a new todo to the list
  }, []); // useCallback ensures the same function reference is used unless dependencies change

  return (
    <>
      {/* Passing the memoized addTodo function and todos to the memoized Todos component */}
      <Todos todos={todos} addTodo={addTodo} />
      <div>Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  );
};

export default App;

Section 16: Production Build & Deployment

16.1. Creating a Production Build

  • Command: npm run build

  • Generates a build folder with minified, uglified, and optimized files for deployment.

  • Key Features:

    • Tree-shaking: Removes unused code.

    • Minification: Reduces file size for faster load times.

    • Source maps: Helps debug production issues.

16.2. Deployment

  1. Automated Deployment Pipelines:

    • What it is: Automating the process of building, testing, and deploying your application using CI/CD tools ensures consistency and speed in deployments.

    • Tools: Common tools include Jenkins, GitHub Actions, GitLab CI, and CircleCI.

    • Steps:

      1. Install Dependencies: Run npm install to ensure all necessary packages are available for the project.

      2. Run Tests: Use commands like npm test or npm run lint to ensure the code is working as expected and adheres to coding standards.

      3. Build the Project: Execute npm run build to create the optimized build folder for production.

      4. Deploy: Push the build to staging (for testing) or production environments (for live users).

  2. Hosting Platforms:

    • AWS S3 + CloudFront:

      • What: Host the React app’s static files (HTML, JS, CSS) on S3 and serve them via CloudFront (a global CDN).

      • How:

        1. Upload the build folder to an S3 bucket.

        2. Configure CloudFront to cache files globally and reduce latency for users.

      • Benefit: Scalable, reliable, and cost-efficient for global distribution.

    • Netlify/Vercel:

      • What: Platforms designed for static site hosting with CI/CD integration.

      • How:

        • Seamlessly integrate with Git for automated deployments.

        • Great for preview builds in pull requests (PRs).

      • Benefit: Developer-friendly and offers instant preview links for staging.

    • On-Premise Servers:

      • What: Hosting the React app on your own servers using tools like nginx or Apache.

      • How:

        1. Copy the build folder to the server.

        2. Configure nginx or Apache to serve static files from the folder.

      • Benefit: Full control over hosting and configuration.

  3. Environment Variables:

    • Use .env files or environment-specific configurations during the build process.

    • Example:

        REACT_APP_API_URL=https://api.production.com
        npm run build
      
  4. Monitoring & Logging:

    • Integrate tools like New Relic, Datadog, or Sentry to monitor performance and capture errors in production.
  5. Cache Management:

    • Techniques to ensure the app loads fast for users while always serving the latest version.

    • Enable long-term caching by generating unique hashes in filenames (done automatically in React builds).

    • Use cache-busting strategies to ensure users always get the latest updates.


Section 17: Advanced Patterns & Additional Topics

17.1. Higher-Order Components (HOCs)

  • A higher-order component takes a component and returns a new component.

  • Commonly used for cross-cutting concerns like logging, analytics, or theming.

function withLogger(WrappedComponent) {
  return function Enhanced(props) {
    console.log('Props:', props);
    return <WrappedComponent {...props} />;
  };
}

//Example:
const withAuthorization = (WrappedComponent) => {
  return (props) => {
    const isLoggedIn = props.isLoggedIn; // Check the authorization status
    if (!isLoggedIn) {
      return <h1>Access Denied</h1>;
    }
    return <WrappedComponent {...props} />;
  };
};

const SecretData = ({ data }) => {
  return <h1>Secret Data: {data}</h1>;
};
const ProtectedData = withAuthorization(SecretData);

export default function App() {
  return <ProtectedData isLoggedIn={true} data="Top Secret!" />;
}

Step-by-Step Flow

  1. App Component:

    • App renders <ProtectedData isLoggedIn={true} data="Top Secret!" />.
  2. ProtectedData:

    • The ProtectedData component is created by wrapping SecretData with the withAuthorization HOC.

    • It checks props.isLoggedIn:

      • If false, renders <h1>Access Denied</h1>.

      • If true, renders the original SecretData component with all props.

  3. SecretData Component:

    • The SecretData component receives data="Top Secret!".

    • Logs Top Secret! to the console.

    • Renders <h1>Secret Data: Top Secret!</h1>.

17.2. Render Props

  • A technique where you pass a function as a prop, which determines what to render.
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };

  // Render a full-page div that tracks mouse movements
  // The "render" prop is called with the current mouse position
  return (
    <div style={{ height: "100vh" }} onMouseMove={handleMouseMove}>
      {render(position)} {/* Dynamically render content based on mouse position */}
    </div>
  );
}

export default function App() {
  return (
    // Pass a render function to the MouseTracker component
   //Render the current mouse position inside an <h1> element
    <MouseTracker
      render={({ x, y }) => (
        <h1>The mouse position is ({x}, {y})</h1>
      )}
    />
  );
}

Wrapping Up: Your React Journey Awaits

Congratulations on making it through this React guide! 🚀 I hope these notes have provided clarity, insights, and the confidence to tackle React with ease. From mastering the basics of components and state to diving into advanced concepts like Hooks, Context API, and Redux, you now have a solid foundation to build web applications.

Remember, React is more than just a library—it's a tool to bring your ideas to life. Whether you're working on personal projects, preparing for interviews, or contributing to professional applications, this guide is here to support you every step of the way.

If you found this blog helpful, don’t forget to:

  • Share it with fellow developers who might benefit from it.

  • Bookmark this guide for quick revision whenever you need it.

  • Engage in the comments—ask questions, share feedback, or suggest additional topics you'd like covered.

React is an ever-evolving ecosystem, and staying curious is the key to staying ahead. Keep experimenting, keep building, and most importantly, keep learning.

Thank you for reading, and happy coding! 🌟
Subham Jaiswal