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
- Section 1: Introduction to React & Key Concepts
- Section 2: Environment Setup (Node, NPM, and Tools)
- Section 3: Create React App & Project Structure
- Section 4: JSX & Rendering
- Section 5: Components (Functional & Class)
- Section 6: Props & State
- Section 7: Lifecycle Methods (Class Components)
- Section 8: React Hooks (Functional Components)
- 1. useState
- 2. useEffect
- 3. useContext
- 4. useReducer
- 5. useRef
- 6. useCallback
- 7. useMemo
- 8. useLayoutEffect
- 9. useDebugValue
- 10. useImperativeHandle
- 11. useInsertionEffect (React 18+)
- 12. useId (React 18+)
- 13. useTransition (React 18+)
- 14. useDeferredValue (React 18+)
- 15. useSyncExternalStore (React 18+)
- Custom Hooks
- Understand Hooks at a Glance
- Section 9: Handling Events & Forms
- Section 10: React Router
- Section 11: Making API Calls & Data Fetching
- Section 12: Context API
- Section 13: Redux Basics
- Section 14: Advanced Redux & Middleware
- Section 15: Performance Optimization
- Section 16: Production Build & Deployment
- Section 17: Advanced Patterns & Additional Topics
- Wrapping Up: Your React Journey Awaits
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.js
x: 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>
insideindex.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:The current state value.
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 aContext.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 anaction
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:
Stores a mutable value that does not cause a re-render when updated.
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
withforwardRef
.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, whilevalue
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:
The name must start with
use
(useLocalStorage
,useAuth
, etc.).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
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.
Additional Hooks:
useLayoutEffect
– LikeuseEffect
, but fires synchronously after DOM changes.useDebugValue
– For dev tools debugging inside custom hooks.useImperativeHandle
– Customizingref
handling withforwardRef
.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.
Concept | Description |
Store | Holds the entire state of your app. |
Action | A plain JavaScript object describing a change, e.g., { type: "INCREMENT" } . |
Reducer | A function taking (state, action) and returning a new state object. |
Dispatch | The 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
Component dispatches
fetchPosts()
(Thunk action).Redux Thunk middleware intercepts and executes
fetchPosts()
.The thunk:
Dispatches
FETCH_POSTS_REQUEST
→ Reducer setsloading: true
.Makes an API call.
Dispatches
FETCH_POSTS_SUCCESS
on success → Reducer updatesposts
with data.Dispatches
FETCH_POSTS_FAILURE
on error → Reducer setserror
.
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 ofprops
andstate
.
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
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:
Install Dependencies: Run
npm install
to ensure all necessary packages are available for the project.Run Tests: Use commands like
npm test
ornpm run lint
to ensure the code is working as expected and adheres to coding standards.Build the Project: Execute
npm run build
to create the optimizedbuild
folder for production.Deploy: Push the build to staging (for testing) or production environments (for live users).
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:
Upload the
build
folder to an S3 bucket.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:
Copy the
build
folder to the server.Configure
nginx
orApache
to serve static files from the folder.
Benefit: Full control over hosting and configuration.
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
Monitoring & Logging:
- Integrate tools like New Relic, Datadog, or Sentry to monitor performance and capture errors in production.
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
App Component:
App
renders<ProtectedData isLoggedIn={true} data="Top Secret!" />
.
ProtectedData:
The
ProtectedData
component is created by wrappingSecretData
with thewithAuthorization
HOC.It checks
props.isLoggedIn
:If
false
, renders<h1>Access Denied</h1>
.If
true
, renders the originalSecretData
component with all props.
SecretData Component:
The
SecretData
component receivesdata="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