React Tutorial for Professionals: Modern Hooks, Redux, Routing, Testing
β What is React?
React is a JavaScript library developed by Facebook for building user interfaces, particularly single-page applications (SPAs). Itβs component-based, declarative, and enables fast UI updates.
π§± Core Concepts in React
1. Components
- Function components β the modern way.
- Class components β legacy but still found in codebases.
2. JSX
JSX is a syntax extension for JavaScript that looks like HTML and is used to describe the UI.
const Hello = () => <h1>Hello World</h1>;
3. Props (Properties)
Used to pass data from parent to child components.
const Welcome = ({ name }) => <h1>Hi {name}</h1>;
4. State
Holds local component data that can change over time.
- Functional: useState
- Class: this.state
π― Functional vs Class Components
| Feature | Class Component | Functional Component |
|---|---|---|
| Syntax | extends React.Component | Plain JavaScript functions |
| State Management | this.state, this.setState() | useState() |
| Lifecycle Methods | componentDidMount, etc. | useEffect() |
| Readability | Verbose | Concise |
| Performance | Slightly slower (more boilerplate) | Faster and optimized with Hooks |
| Modern Best Practice | β Legacy | β Recommended |
| Access to Hooks | β | β |
π§ Functional Component Example
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
ποΈ Class Component Example
import React from "react";
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<h2>{this.state.count}</h2>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
π Folder Structure (Best Practice)
src/
β
βββ components/
β βββ Button.jsx
βββ pages/
β βββ Home.jsx
βββ hooks/
β βββ useFetch.js
βββ context/
β βββ AuthContext.js
βββ App.jsx
βββ index.js
π Real DOM vs Virtual DOM in React
| Feature | Real DOM | Virtual DOM |
|---|---|---|
| Definition | Actual DOM rendered by browser | In-memory JS object representation |
| Update Speed | Slow | Fast (via diffing and batching) |
| Efficiency | Low (re-renders whole tree) | High (only updates changes) |
| Re-rendering | Full tree reflow | Selective updates |
| Use in React | Browser managed | React managed |
| Example Frameworks | jQuery, Vanilla JS | React, Vue, Angular (conceptually) |
β Feature of React
Sure! Here are the key features of React in short:
- JSX β JavaScript syntax extension to write HTML-like code.
- Components β Reusable, modular UI blocks.
- Virtual DOM β Efficient UI updates via diffing.
- One-way Data Binding β Data flows from parent to child.
- Hooks β Use state and lifecycle in functional components.
- Declarative UI β Describe what the UI should look like.
- Unidirectional Flow β Predictable data handling.
- React Native Support β Build mobile apps using React.
- Strong Ecosystem β React Router, Redux, etc.
- Community & Tooling β Large support and dev tool
β React Limitation
Here are the limitations of React (short and clear):
- JSX Complexity β JSX can be confusing for beginners.
- Only UI Layer β React handles view only, needs other libraries for routing, state, etc.
- Fast Updates Can Break Things β Frequent changes require constant learning.
- SEO Limitations β Without SSR (like Next.js), React apps can struggle with SEO.
- Boilerplate Code β Setting up large apps needs config and structure.
- Learning Curve β Advanced concepts (hooks, context, refs) can be complex.
- Poor Documentation for Ecosystem Tools β Some third-party libraries are under-documented.
- Too Many Choices β State management, routing, etc., have many competing options.
β JSX (JavaScript XML)
JSX is a syntax extension for JavaScript used in React to describe UI elements in a way that looks like HTML.
πΉ Example:
const element = <h1>Hello, world!</h1>;
This looks like HTML but is actually converted to JavaScript under the hood using Babel:
const element = React.createElement("h1", null, "Hello, world!");
π§ Virtual DOM β Explained Simply
The Virtual DOM (VDOM) is a lightweight JavaScript copy of the Real DOM used by React to efficiently update the UI.
πΉ How It Works:
- UI is built in Virtual DOM.
- When state/props change, a new VDOM is created.
- React compares (diffs) old vs new VDOM.
- Only the changed parts are updated in the Real DOM.
β Benefits:
- Faster UI updates
- Improved performance
- Efficient rendering
- Better user experience
β Why Doesnβt the Browser Read JSX?
Because JSX is not valid JavaScript or HTML β it’s a syntax extension that browsers donβt understand natively.
Before JSX runs in the browser, tools like Babel convert it into pure JavaScript:
π React ES6 vs ES5 β Key Differences
React code can be written using ES5 (older JavaScript) or ES6+ (modern JavaScript with new features). Modern React prefers ES6+ due to cleaner syntax and better support for components.
π Comparison Table: ES5 vs ES6 in React
| Feature | ES5 (Old Way) | ES6 (Modern Way) |
|---|---|---|
| Component Creation | React.createClass() | class extends React.Component |
| Variable Declaration | var | let / const |
| Function Declaration | function () {} | Arrow function () => {} |
Binding this | Manual bind in constructor | Arrow functions auto-bind this |
| Props Access | this.props.name | this.props.name (same) |
| State Initialization | Inside getInitialState() | Inside constructor or hooks |
| Imports | var React = require('react') | import React from 'react' |
| Export | module.exports = Component | export default Component |
π React vs Angular β Quick Comparison
| Feature | React | Angular |
|---|---|---|
| Type | Library for UI | Full-fledged framework |
| Language | JavaScript + JSX | TypeScript |
| Architecture | View (V in MVC) | MVC/MVVM |
| Learning Curve | Easy to moderate | Steeper (more concepts) |
| DOM | Virtual DOM | Real DOM with change detection |
| Data Binding | One-way | Two-way and one-way |
| Component-Based | β Yes | β Yes |
| State Management | External (Redux, Context, etc.) | Built-in services & RxJS |
| Routing | External (React Router) | Built-in |
| Performance | Fast with VDOM | Slightly heavier but optimized |
| Mobile Support | React Native | Angular with Ionic or NativeScript |
| Community & Ecosystem | Huge community, flexible choices | Strong, opinionated ecosystem |
| Use Case | SPAs, UIs, lightweight apps | Enterprise-level, large-scale apps |
β
Choose React: For flexibility, lightweight UIs, and faster development
β
Choose Angular: For structured enterprise apps with built-in tooling
π render() and React.Fragment in React
π§± render() Method
- Used only in class components.
- It defines what to show on the screen.
β Syntax:
class MyComponent extends React.Component {
render() {
return <h1>Hello, world!</h1>;
}
}
πΉ Key Points:
- Required in every class component.
- Should return only one parent element (can be a div or Fragment).
π§© React.Fragment
- Used to group multiple elements without adding extra DOM nodes.
- Helps avoid unnecessary
<div>wrappers.
β Syntax:
return (
<React.Fragment>
<h1>Title</h1>
<p>Description</p>
</React.Fragment>
);
β Shorter version using empty tags:
return (
<>
<h1>Title</h1>
<p>Description</p>
</>
);
π Props vs State in React
| Feature | Props | State |
|---|---|---|
| Definition | Data passed from parent to child | Local data managed inside a component |
| Mutability | Immutable (read-only) | Mutable (can change with setState/useState) |
| Use Case | For configuration/input | For tracking data that changes over time |
| Who Controls It | Parent component | Component itself |
| Accessibility | Available via this.props or props | Available via this.state or [state, setState] |
| Lifecycle Impact | Does not trigger re-renders by itself | Triggers re-render when updated |
| Functional Use | Passed as arguments in function components | Managed using useState() in function components |
β Example of Props:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
β Example of State:
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
π§ Summary
Use state to manage dynamic internal data of a component.
Use props to pass data into a component.
π Updating State in Class Components using this.setState()
In React class components, you never modify this.state directly. Instead, use this.setState() to update the componentβs state and trigger a re-render.
β Syntax:
this.setState({ key: newValue });
π§ Example:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<h2>{this.state.count}</h2>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
β οΈ Important Notes:
- Asynchronous:
this.setState()is async. Donβt rely onthis.stateright after setting it. - Using Previous State: Use a function for safe updates based on previous state.
this.setState(prevState => ({
count: prevState.count + 1
}));
- Triggers Re-render: Every
setState()call updates the component and re-renders the UI.
π What does super(props); mean in React?
πΉ Why is it used?
super(props)calls the parent constructor (React.Component) and passes thepropsto it.- It allows you to use
this.propsinside the constructor.
Without calling super(props), this is undefined, and accessing this.props will throw an error.
π Arrow Functions in React
Arrow functions (=>) are widely used in React for cleaner and more concise code, especially in event handlers, functional components, and class methods.
β Basic Syntax:
const add = (a, b) => a + b;
β 1. Arrow Functions in Functional Components
const Hello = () => {
return <h1>Hello World</h1>;
};
Or shorter:
const Hello = () => <h1>Hello World</h1>;
β 2. Arrow Functions as Event Handlers (Class Components)
class Button extends React.Component {
state = { count: 0 };
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return <button onClick={this.handleClick}>Click {this.state.count}</button>;
}
}
πΉ Why use arrow functions here?
- Automatically bind
this - No need for manual binding in constructor
β 3. Arrow Functions Inline
<button onClick={() => console.log("Clicked")}>Click me</button>
β οΈ Note:
- Avoid using inline arrow functions in performance-critical areas (can cause re-renders).
π§ Key Benefits:
| Feature | Benefit |
|---|---|
this binding | No need for manual binding |
| Concise syntax | Cleaner and shorter function syntax |
| Best with hooks | Used extensively in modern React |
π Stateful vs Stateless Components in React β Quick Comparison
| Feature | Stateful Component | Stateless Component |
|---|---|---|
| Holds State | β
Yes (manages its own state) | β No state (pure UI rendering) |
| Re-renders | Based on state or props changes | Based on props only |
| Type | Class or functional with useState() | Functional (no state/hooks) |
| Use Case | Dynamic components (e.g., form, counter) | UI-only components (e.g., header, label) |
| Lifecycle Methods | Available in class components | Not applicable |
| Complexity | More complex | Simple and reusable |
β
Stateful Component Example (with useState):
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
β Stateless Component Example:
function Greeting(props) {
return <h1>Hello, {props.name}</h1>;
}
π Different Phases of a React Component’s Lifecycle (Class-Based)
React class components go through three main lifecycle phases:
π 1. Mounting Phase
Component is created and inserted into the DOM.
π§ Lifecycle Methods:
| Method | Purpose |
|---|---|
constructor() | Initialize state and bind methods |
static getDerivedStateFromProps() | Sync state from props (rarely used) |
render() | Returns JSX to render the UI |
componentDidMount() | Called once after component is rendered |
π 2. Updating Phase
Triggered when props or state changes.
π§ Lifecycle Methods:
| Method | Purpose |
|---|---|
static getDerivedStateFromProps() | Called on update too |
shouldComponentUpdate() | Decide whether to re-render or not |
render() | Re-renders UI |
getSnapshotBeforeUpdate() | Captures info (like scroll) before update |
componentDidUpdate() | Invoked after DOM updates |
β 3. Unmounting Phase
Component is removed from the DOM.
π§ Lifecycle Method:
| Method | Purpose |
|---|---|
componentWillUnmount() | Cleanup: remove timers, listeners, etc. |
π Optional: Error Handling Phase
React also includes error handling lifecycle methods.
| Method | Purpose |
|---|---|
componentDidCatch() | Catch errors in child components |
static getDerivedStateFromError() | Update UI when an error occurs |
β Modern Functional Components Lifecycle (with Hooks)
| Phase | Hook |
|---|---|
| Mount | useEffect(() => { ... }, []) |
| Update | useEffect(() => { ... }, [deps]) |
| Unmount | useEffect(() => { return () => {...} }, []) |
π’ Order of React Lifecycle Methods (Class Components)
Here is the exact order in which React class component lifecycle methods are called:
π 1. Mounting Phase (Component Creation)
| Step | Method | Purpose |
|---|---|---|
| 1οΈβ£ | constructor() | Initialize state and bindings |
| 2οΈβ£ | static getDerivedStateFromProps() | Sync state with props (rare use) |
| 3οΈβ£ | render() | Return JSX |
| 4οΈβ£ | componentDidMount() | Perform side-effects (API calls, etc.) |
π 2. Updating Phase (State or Props Change)
Triggered by setState() or receiving new props.
| Step | Method | Purpose |
|---|---|---|
| 1οΈβ£ | static getDerivedStateFromProps() | Again (if needed) |
| 2οΈβ£ | shouldComponentUpdate() | Decide whether to re-render |
| 3οΈβ£ | render() | Re-render UI |
| 4οΈβ£ | getSnapshotBeforeUpdate() | Capture info before DOM changes |
| 5οΈβ£ | componentDidUpdate() | Reacts to the update |
β 3. Unmounting Phase (Component Removal)
| Step | Method | Purpose |
|---|---|---|
| 1οΈβ£ | componentWillUnmount() | Cleanup (timers, subscriptions, etc.) |
β οΈ 4. Error Handling Phase (If Error Occurs)
| Step | Method | Purpose |
|---|---|---|
| 1οΈβ£ | static getDerivedStateFromError() | Set fallback UI |
| 2οΈβ£ | componentDidCatch() | Log or handle error details |
β
Full Example: Class Component Lifecycle
import React from "react";
class LifecycleDemo extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
console.log("1οΈβ£ constructor");
}
static getDerivedStateFromProps(props, state) {
console.log("2οΈβ£ getDerivedStateFromProps");
return null; // No state update from props
}
componentDidMount() {
console.log("4οΈβ£ componentDidMount");
}
shouldComponentUpdate(nextProps, nextState) {
console.log("5οΈβ£ shouldComponentUpdate");
return true; // allow update
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("6οΈβ£ getSnapshotBeforeUpdate");
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("7οΈβ£ componentDidUpdate");
}
componentWillUnmount() {
console.log("8οΈβ£ componentWillUnmount");
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
console.log("3οΈβ£ render");
return (
<div>
<h2>Count: {this.state.count}</h2>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
export default LifecycleDemo;
π What is an Event in React?
An event in React is an action or user interaction (like a click, input, or keypress) that triggers a function to run β similar to JavaScript DOM events.
β Example of Event in React:
function MyButton() {
const handleClick = () => {
alert("Button clicked!");
};
return <button onClick={handleClick}>Click Me</button>;
}
onClickis the event listenerhandleClickis the event handler function
π§ What is a Synthetic Event in React?
A SyntheticEvent is React’s cross-browser wrapper around the native browser event.
It wraps native events (like click, submit, change, etc.) and normalizes them so they behave consistently across all browsers.
π§ Features of SyntheticEvent:
- Has the same interface as a native browser event.
- Works identically across browsers (standardized behavior).
- Supports event pooling (for performance, but deprecated in React 17+).
- Supports preventDefault() and stopPropagation() like native events.
β Example Using SyntheticEvent:
function InputExample() {
const handleChange = (event) => {
console.log("Input value:", event.target.value); // SyntheticEvent
};
return <input type="text" onChange={handleChange} />;
}
Here, event is a SyntheticEvent, not a raw DOM event.
π What is ref in React?
ref (reference) is used in React to access and interact with DOM elements or React elements directly β outside the normal React data flow.
β
Use Cases of ref:
Integrate with third-party libraries
Focus or blur an input element
Trigger animations
Read or modify DOM properties directly
import React, { useRef } from "react";
function TextInputFocus() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus(); // Access the DOM element directly
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}
π§ How to Modularize Code in React
Modularizing code means breaking your app into small, reusable, and manageable components and files. This improves readability, maintainability, testing, and scalability.
Break large components into smaller, reusable ones.
Use a clear folder structure:
use export and import properties
π React.forwardRef() β Explained Simply
React.forwardRef() is a special React function that allows you to forward a ref from a parent to a child component β especially when the child is a functional component.
Normally, refs donβt work directly on functional components because they donβt have an instance. forwardRef fixes that by passing the ref down to a DOM element or another component.
π Why use forwardRef()?
- When you want parent access to child DOM elements (e.g., focus, scroll, select).
- When using 3rd-party component libraries that require DOM refs.
- To build reusable, composable input components.
import React, { useRef } from "react";
// π Child component using forwardRef
const CustomInput = React.forwardRef((props, ref) => {
return <input type="text" ref={ref} placeholder="Type here" />;
});
function App() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus(); // Access input inside child
};
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
export default App;
π React.createRef() in React
React.createRef() is used in class components to create a ref object that can be attached to a DOM element or React component to directly access it.
import React from "react";
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef(); // π Create ref
}
focusInput = () => {
this.inputRef.current.focus(); // π Access the DOM node
};
render() {
return (
<div>
<input type="text" ref={this.inputRef} />
<button onClick={this.focusInput}>Focus Input</button>
</div>
);
}
}
export default MyComponent;
π How to Create a Form in React
Forms in React are created using controlled components, where the form inputs are connected to component state using useState() (for functional components) or this.state (for class components).
β
Functional Component Example (Using useState)
import React, { useState } from 'react';
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: ''
});
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value // dynamically updates 'name' or 'email'
});
};
const handleSubmit = (e) => {
e.preventDefault();
alert(`Name: ${formData.name}, Email: ${formData.email}`);
};
return (
<form onSubmit={handleSubmit}>
<label>
Name: <input type="text" name="name" value={formData.name} onChange={handleChange} />
</label><br />
<label>
Email: <input type="email" name="email" value={formData.email} onChange={handleChange} />
</label><br />
<button type="submit">Submit</button>
</form>
);
}
export default ContactForm;
β Class Component Example (Optional)
class ContactForm extends React.Component {
constructor(props) {
super(props);
this.state = { name: '', email: '' };
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
}
handleSubmit = (e) => {
e.preventDefault();
alert(`Name: ${this.state.name}, Email: ${this.state.email}`);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input name="name" value={this.state.name} onChange={this.handleChange} />
<input name="email" value={this.state.email} onChange={this.handleChange} />
<button type="submit">Submit</button>
</form>
);
}
}
π Controlled vs Uncontrolled Components in React
These are two ways to handle form inputs in React.
β Controlled Component
A component where form data is handled by React state.
π§ Characteristics:
- Uses
useState(orthis.state) - React controls the inputβs value
- Always updates via
onChange
π§ͺ Example:
function ControlledInput() {
const [value, setValue] = React.useState('');
return (
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
β Advantages:
- Easier validation
- Single source of truth
- React has full control
β Uncontrolled Component
A component where form data is handled by the DOM (not React).
π§ Characteristics:
- Uses
refto access DOM node - No state tracking of input value
- Browser manages the inputβs current value
π§ͺ Example:
function UncontrolledInput() {
const inputRef = React.useRef();
const handleSubmit = () => {
alert(inputRef.current.value); // Access DOM value directly
};
return (
<>
<input type="text" ref={inputRef} />
<button onClick={handleSubmit}>Submit</button>
</>
);
}
β Advantages:
- Simpler for quick forms
- Less React code
- Slightly better performance in large forms
π Comparison Table
| Feature | Controlled | Uncontrolled |
|---|---|---|
| Data Source | React State | DOM |
| Access Value | state | ref.current.value |
| React Control | Full | Minimal |
| Use Case | Validation, dynamic UI, logic-based forms | Simple or 3rd-party uncontrolled forms |
| Re-render on input | β Yes | β No |
π§ Summary:
- Use Controlled Components for complex logic, validation, or multi-step forms.
- Use Uncontrolled Components for simpler or read-only scenarios.
π What is a HOC (Higher-Order Component) in React?
A Higher-Order Component (HOC) is a function that takes a component as input and returns a new enhanced component.
Think of it as a component wrapper that adds reusable behavior (like logging, access control, or data fetching).
const EnhancedComponent = higherOrderComponent(WrappedComponent);
π§ Real Example:
// HOC that adds loading spinner behavior
function withLoading(Component) {
return function EnhancedComponent({ isLoading, ...props }) {
if (isLoading) return <p>Loading...</p>;
return <Component {...props} />;
};
}
β Usage:
function UserList({ users }) {
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
const UserListWithLoading = withLoading(UserList);
// Rendering this in JSX:
<UserListWithLoading isLoading={false} users={['Ved', 'Amit']} />
π Key Characteristics:
| Feature | Description |
|---|---|
| Type | Pure function (no JSX inside HOC itself) |
| Reusability | Adds logic to multiple components |
| Props Passing | Must pass down ...props to wrapped component |
| Naming | Convention: withSomething (e.g., withAuth) |
| Similar To | Like a middleware for React components |
β οΈ Common Use Cases:
- Authentication checks (
withAuth) - Loading spinners (
withLoading) - Error boundaries
- Logging/metrics
- Code reuse for data fetching
π What is the Props Proxy Pattern?
In a props proxy HOC, the HOC intercepts and manipulates props before passing them to the wrapped component.
function withPropsProxy(WrappedComponent) {
return function EnhancedComponent(props) {
const newProps = {
...props,
injectedProp: 'new property',
};
return <WrappedComponent {...newProps} />;
};
}
π What is a Pure Component in React?
A Pure Component in React is a class component that implements automatic shallow comparison in its shouldComponentUpdate() method to prevent unnecessary re-renders.
π Example:
class RegularComponent extends React.Component {
render() {
console.log('RegularComponent rendered');
return <div>{this.props.message}</div>;
}
}
class OptimizedComponent extends React.PureComponent {
render() {
console.log('PureComponent rendered');
return <div>{this.props.message}</div>;
}
}
Now, if message doesnβt change:
RegularComponentwill still renderPureComponentwill skip rendering
π What is key in React?
In React, a key is a special string attribute you must include when rendering a list of elements. It helps React identify which items have changed, added, or removed efficiently during re-rendering.
const users = ['Ved', 'Amit', 'Sara'];
function UserList() {
return (
<ul>
{users.map((user, index) => (
<li key={user}>{user}</li> // π key is important here
))}
</ul>
);
}
π What is React Redux?
Redux is a state management library for JavaScript apps, often used with React. It allows you to centralize application state and manage it in a predictable way.
πΊ 3 Core Principles of Redux
- Single Source of Truth
- All app state is stored in a single store.
- State is Read-Only
- The only way to change state is to dispatch an action, not mutate it directly.
- Changes via Pure Functions (Reducers)
- State transitions are handled by pure functions called reducers.
π§± Key Components of Redux
| Component | Description |
|---|---|
| Actions | Plain JS objects describing what happened |
| Reducers | Functions that take state & action β return new state |
| Store | Holds the application state and provides methods to access, dispatch, and subscribe |
| Provider | React-Redux component that makes the Redux store available to child components |
| connect / hooks | To connect React components to the Redux store (e.g. useSelector, useDispatch) |
π Data Flow in Redux
Component β dispatch(action)
β
Reducer (pure function)
β
New State (updated store)
β
React Component re-renders
π§ Defining an Action
An action is a plain object with a type field:
// action.js
export const increment = () => ({
type: 'INCREMENT'
});
You can also pass a payload:
export const setUser = (user) => ({
type: 'SET_USER',
payload: user
});
π§ Role of Reducers
A reducer is a pure function that receives the current state and an action, and returns the new state:
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
};
π¬ What is a Redux Store?
The store is the central location that:
- Holds the state
- Allows access via
getState() - Allows updates via
dispatch(action) - Allows subscription via
subscribe()
import { createStore } from 'redux';
const store = createStore(counterReducer);
π¦ React-Redux Integration
// index.js
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
<Provider store={store}>
<App />
</Provider>
In your component:
import { useSelector, useDispatch } from 'react-redux';
const Counter = () => {
const count = useSelector(state => state);
const dispatch = useDispatch();
return (
<div>
<h1>{count}</h1>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
</div>
);
};
β
store.subscribe() β What it does
It registers a callback function that gets called every time the Redux storeβs state changes.
π§ͺ Syntax:
const unsubscribe = store.subscribe(() => {
console.log('State changed:', store.getState());
});
subscribe()returns an unsubscribe function.- Call it to stop listening to changes.
π Flux vs Redux
| Feature | Flux | Redux |
|---|---|---|
| Architecture | Multiple stores | Single store |
| Dispatcher | Required | Not used |
| Data Mutation | Mutable via store | Immutable via reducer |
| State Storage | Inside multiple stores | Inside a single store |
| Boilerplate | More complex | Simplified with Redux Toolkit |
β Advantages of Redux
- π Predictable state updates
- avoid prop drilling
- π Centralized global state
- π§ͺ Easier debugging (DevTools)
- π€ Decouples state from components
- π§ Great for large-scale applications
- π Time-travel debugging
- π§© Ecosystem: middleware, dev tools, toolkit
There are several alternatives to Redux for state management in React, depending on your appβs complexity, scalability needs, and developer preference. some example: ContextApi, MOBX, Appolo Client + GraphQL, RXJS
π Redux vs Context API in React β Whatβs the Difference?
Both Redux and React Context API help in managing global state, but they are fundamentally different tools with different use cases, power, and complexity. Context Api is built-in React feature for passing global data (like themes, user info, etc.) without prop drilling. while Redux is a standalone state management library (can be used outside React) that manages application state using a central store, actions, and reducers.
π₯ Main Difference Between redux and react-redux
redux | react-redux | |
|---|---|---|
| What it is | Core library to create and manage global state | Binding library to connect Redux to React components |
| Provides | createStore(), combineReducers(), applyMiddleware() | Provider, useDispatch(), useSelector(), Note: connect() Before React Hooks (useSelector, useDispatch), this was the primary way to access Redux state and dispatch actions in class-based or functional components. |
| Usage | Used to build the Redux store and reducers | Used to make React components aware of Redux state |
| React-specific? | β No (can be used with Vue, Angular, etc.) | β Yes (designed only for React) |
π What is React Router?
React Router is a popular library used in React applications to handle client-side routing β i.e., navigating between different views/components without refreshing the browser.
π§± Core Concepts in React Router
| Concept | Description |
|---|---|
BrowserRouter | Enables routing using browserβs history API (clean URLs) |
Routes | Container for all route definitions |
Route | Defines the path and the component to render |
Link / NavLink | Used for navigation (like <a>, but without reload) |
useParams, useNavigate, useLocation | React Router hooks for dynamic routing, navigation, etc. |
β Example: Basic React Router Setup
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
import Home from './Home';
import About from './About';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
π What is BrowserRouter?
- Uses HTML5 history API (
pushState,popState) for clean URLs (e.g.,/home,/about) - Keeps the UI in sync with the URL without reloading the page
- Must wrap your app inside it to enable routing
β Advantages of React Router
| Advantage | Description |
|---|---|
| SPA Support | Enables Single Page Application behavior |
| No Page Reloads | Navigation is fast and dynamic |
| Dynamic Routing | Supports params, nested routes, and lazy loading |
| Route Protection | Can add guards based on auth logic |
| Clean URLs | Uses browser history (BrowserRouter) |
| Hook Support | useParams, useNavigate, etc., simplify routing logic |
π Conventional Routing vs React Routing
| Feature | Conventional Routing (e.g., PHP, server-rendered) | React Routing (SPA) |
|---|---|---|
| Routing Type | Server-side | Client-side (JS in browser) |
| Page Reload | Full reload on every route change | No reload |
| Speed | Slower (due to server calls) | Faster (JS handles it instantly) |
| Data Fetch | Each route triggers a new request | Can fetch data conditionally |
| URL Mapping | Server determines what page to serve | React component renders view |
| SEO | Better out of the box | Needs SSR (e.g. Next.js) for SEO |
π Extra: Nested Routes Example
<Routes>
<Route path="/dashboard" element={<Dashboard />}>
<Route path="settings" element={<Settings />} />
<Route path="profile" element={<Profile />} />
</Route>
</Routes>
URL example: /dashboard/settings
π setState() vs replaceState() in React
β
setState()
- Merges the new state with the existing state.
- Partial update β only updates the keys you specify.
- Commonly used in both class components and
β replaceState() (Deprecated)
- Replaces the entire state object instead of merging.
π Components of React Router: BrowserRouter vs HashRouter vs MemoryRouter
React Router provides several router components to control how the routing works depending on the environment and URL handling needs.
π 1. BrowserRouter β Most Common for Web Apps
β
Uses the HTML5 History API (pushState, popState)
π URL looks clean: /home, /about
β Example:
import { BrowserRouter, Route, Routes } from 'react-router-dom';
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
β Requirements:
- Server must be configured to return
index.htmlfor all routes (otherwise 404 on refresh)
π’ 2. HashRouter β For Static File Hosts (GitHub Pages, etc.)
β
Uses the hash (#) portion of the URL
π URLs look like: http://example.com/#/about
β Example:
import { HashRouter, Route, Routes } from 'react-router-dom';
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</HashRouter>
π§ Good For:
- Apps hosted on static file servers with no server-side routing support
π§ 3. MemoryRouter β In-Memory History (No URL changes)
β
Keeps routing in memory (no interaction with browser URL)
π§ͺ Commonly used in unit tests, React Native, or non-browser environments
β Example:
import { MemoryRouter, Route, Routes } from 'react-router-dom';
<MemoryRouter initialEntries={['/about']}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</MemoryRouter>
β‘ Boost React Performance β Top Tips
β‘ 1. Use React.memo() for Functional Components
Wrap components with React.memo to avoid re-rendering unless props change.
const MyComponent = React.memo(function MyComponent(props) {
return <div>{props.value}</div>;
});
π§ 2. Use useCallback and useMemo
Prevent unnecessary re-creation of functions and values.
const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);
const memoizedValue = useMemo(() => computeExpensiveValue(x), [x]);
ποΈ 3. Code Splitting with React.lazy() and Suspense
Split code by routes/components to reduce initial load size.
const LazyComponent = React.lazy(() => import('./MyComponent'));
π§Ή 4. Avoid Inline Functions in JSX
They create a new function on every render, breaking memoization.
β Bad:
<button onClick={() => doSomething()}>Click</button>
β Good:
const handleClick = useCallback(() => doSomething(), []);
<button onClick={handleClick}>Click</button>
π§± 5. Use Key Wisely in Lists
Avoid using array indices as keys in dynamic lists. Use stable unique IDs.
π§© 6. Virtualize Long Lists
Use libraries like react-window or react-virtualized for large lists.
π 7. Debounce Expensive Operations
Like search inputs or resize events β use lodash.debounce or useDebounce.
π§ͺ 8. Keep Component State Local Where Needed
Avoid putting everything in global context or Redux β local state is faster.
π« 9. Avoid Unnecessary Re-renders
Use tools like:
- React Developer Tools
why-did-you-renderlibrary (for debugging excessive renders)
π 10. Enable Production Build
For deployment, always build with npm run build to enable minification and performance optimizations.
π What is Shallow Comparison in React?
Shallow comparison means comparing primitive values directly and objects/arrays by reference, not by their internal contents.
Used mainly in:
React.PureComponentReact.memo()shouldComponentUpdate()
β οΈ Limitation
It does NOT check deep equality of nested values:
const a = { user: { name: "Ved" } };
const b = { user: { name: "Ved" } };
a === b; // false β different references
So if you update nested object values without changing reference, React may not re-render.
π React Context API (v16.3+)
The Context API (introduced in React 16.3) provides a way to share global data (like themes, auth, language, etc.) across components without passing props manually at every level.
π§± Main Parts of Context API
| Part | Description |
|---|---|
React.createContext() | Creates a Context object |
Provider | Provides context data to the component tree |
Consumer | Consumes context data (older way) |
useContext() | Hook to consume context in functional components (from v16.8) |
β Example (React 16.3 β Class Component)
// 1. Create Context
const MyContext = React.createContext();
// 2. Create Provider
class MyProvider extends React.Component {
state = {
name: 'Ved',
role: 'Admin'
};
render() {
return (
<MyContext.Provider value={this.state}>
{this.props.children}
</MyContext.Provider>
);
}
}
// 3. Consume in Class Component
class MyConsumer extends React.Component {
render() {
return (
<MyContext.Consumer>
{context => (
<div>
<p>Name: {context.name}</p>
<p>Role: {context.role}</p>
</div>
)}
</MyContext.Consumer>
);
}
}
β
Modern Way (Post-16.8: Functional + useContext())
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext();
const App = () => {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
};
const Toolbar = () => {
return <ThemedButton />;
};
const ThemedButton = () => {
const theme = useContext(ThemeContext); // β
Consume context
return <button className={theme}>I am styled with {theme} theme</button>;
};
π§ Why Use Context API?
| Use Case | Example |
|---|---|
| Theme switching | Light/Dark mode |
| Authentication info | User logged in status, token |
| Language localization | English / Hindi, etc. |
| Global config or settings | API URLs, toggles |
π¦ What is PropTypes in React?
PropTypes is a built-in React utility (via the prop-types package) used to validate props passed to a component. It helps catch bugs by ensuring the right type of props are passed at runtime.
//npm install prop-types
import React from 'react';
import PropTypes from 'prop-types';
function Welcome({ name, age }) {
return <h1>Hello {name}, age {age}</h1>;
}
// β
PropTypes definition
Welcome.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number
};
// β
Default props (optional)
Welcome.defaultProps = {
age: 18
};
export default Welcome;
π§© Common PropTypes
| PropTypes Syntax | Description |
|---|---|
PropTypes.string | Must be a string |
PropTypes.number | Must be a number |
PropTypes.bool | Must be a boolean |
PropTypes.func | Must be a function |
PropTypes.array | Must be an array |
PropTypes.object | Must be an object |
PropTypes.node | Anything renderable (string, element, fragment, etc.) |
PropTypes.element | A React element |
PropTypes.any | Any type |
PropTypes.symbol | A JS Symbol |
π§ͺ Advanced Examples
πΈ Optional & Required:
ttitle: PropTypes.string, // optional
title: PropTypes.string.isRequired, // required
πΈ One of Specific Values
size: PropTypes.oneOf(['small', 'medium', 'large']),
πΈ One of Multiple Types
id: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
πΈ Array of Specific Types
scores: PropTypes.arrayOf(PropTypes.number),
πΈ Object of Specific Shape
user: PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string.isRequired
}),
π What is Memoization in React?
Memoization is an optimization technique that caches the result of a function or component so it only re-computes or re-renders when inputs change. This avoids unnecessary work and boosts performance.
β React Memoization Tools
| Tool | Use Case |
|---|---|
React.memo | Memoize a component |
useMemo() | Memoize a value/result |
useCallback() | Memoize a function |
1οΈβ£ React.memo (Component-level Memoization)
Prevents re-rendering unless props change. React.memo is just a HOC.
const MyComponent = React.memo(({ name }) => {
console.log("Rendering MyComponent");
return <h1>Hello {name}</h1>;
});
2οΈβ£ useMemo() (Value-level Memoization)
Caches expensive values unless dependencies change.
const expensiveValue = useMemo(() => {
return computeHeavyTask(input);
}, [input]);
3οΈβ£ useCallback() (Function-level Memoization)
Caches functions so they donβt get recreated on every render.
const handleClick = useCallback(() => {
console.log("Clicked!");
}, []);
π‘ API Calls in React β GET, POST, PUT, DELETE
Learn how to make different types of API requests in React using fetch() and axios.
π HTTP Methods Overview
| Method | Purpose |
|---|---|
| GET | Retrieve data |
| POST | Add new data |
| PUT | Update existing data |
| DELETE | Remove data |
π Example API Endpoints
| HTTP Method | Example Path | Use Case |
|---|---|---|
| GET | /api/users | Fetch all users |
| POST | /api/users | Add a new user |
| PUT | /api/users/:id | Update user by ID |
| DELETE | /api/users/:id | Delete user by ID |
βοΈ Using fetch() (Native)
β GET
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []);
β POST
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Ved', age: 30 })
});
β PUT
fetch('/api/users/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ age: 31 })
});
β DELETE
fetch('/api/users/1', {
method: 'DELETE'
});
βοΈ Using axios (Recommended)
β Setup
npm install axios
β Usage
import axios from 'axios';
// GET
axios.get('/api/users').then(res => setUsers(res.data));
// POST
axios.post('/api/users', { name: 'Ved', age: 30 });
// PUT
axios.put('/api/users/1', { age: 31 });
// DELETE
axios.delete('/api/users/1');
π§ React Hooks β Complete Guide (Latest Version)
Hooks let you use state and other React features in functional components β without writing class components.
β
1. useState()
π Description:
useState allows functional components to have local state. It’s the most basic and frequently used hook.
π§ͺ Syntax:
const [state, setState] = useState(initialValue);
β
2. useEffect()
π Description:
useEffect is used to perform side effects in function components, such as:
- Data fetching
- Subscribing/unsubscribing
- DOM manipulation
- Setting up timers
It replaces lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount.
π§ͺ Syntax:
useEffect(() => {
// effect logic
return () => {
// cleanup logic (optional)
};
}, [dependencies]);
π§ Example β Fetching Data:
import { useEffect, useState } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []); // Empty dependency array β run once after mount
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
π useEffect() Dependency Array Behavior
| Dependency Array | Behavior in Class Component (Equivalent) |
|---|---|
[] | β
componentDidMount (runs once on mount) |
[count] | β
componentDidUpdate (runs when count changes) |
| (no array) | β
Both componentDidMount and componentDidUpdate β runs after every render (initial + updates) |
| Class Component | Functional Component Equivalent |
|---|---|
componentWillUnmount | useEffect(() => { return () => {} }, []) |
β
3. useContext()
π Description:
useContext() is used to access context values in a functional component β replacing the older <Context.Consumer> pattern. It allows you to share global data (like theme, user info, language, etc.) across the component tree without passing props manually at every level.
π§ͺ Syntax:
const value = useContext(MyContext);
π§ Example:
// 1. Create context
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
// 2. Use context
function Toolbar() {
const theme = useContext(ThemeContext);
return <button className={theme}>I am {theme} themed</button>;
}
β
4. useRef()
π Description:
useRef() is a React Hook that:
- Gives you a mutable reference object that persists across renders.
- Commonly used to access DOM elements directly.
- Also used to store values that donβt trigger re-render when updated.
import { useRef } from 'react';
function TextInput() {
const inputRef = useRef();
const focusInput = () => {
inputRef.current.focus(); // Access DOM element directly
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
β
5. useMemo()
π Description:
useMemo() is a React hook used to memoize (cache) expensive calculations so they are only recomputed when their dependencies change.
π Example: Expensive Calculation
import { useMemo, useState } from 'react';
function FibonacciCalculator({ n }) {
const fib = useMemo(() => {
console.log("Calculating Fibonacci...");
const fibonacci = (n) => {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
};
return fibonacci(n);
}, [n]);
return <h3>Fib({n}) = {fib}</h3>;
}
β οΈ Without useMemo:
The expensive fibonacci() function would re-run on every render, even if n hasnβt changed. Thatβs slow.
β
6. useCallback()
π Description:
useCallback() is a React hook that returns a memoized version of a function, which is only re-created when one of its dependencies changes. Use it to prevent unnecessary re-creations of functions β especially helpful when passing callbacks to child components.
π Example:
//syntax
const memoizedCallback = useCallback(() => {
// function logic
}, [dependencies]);
//codeimport { useState, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(prev => prev + 1);
}, []); // Will not re-create on each render
return (
<div>
<p>Count: {count}</p>
<Button onClick={increment} />
</div>
);
}
function Button({ onClick }) {
console.log("Button rendered");
return <button onClick={onClick}>Increment</button>;
}
β
7. useReducer()
π Description:
useReducer() is an alternative to useState() for managing complex state logic β especially when:
- State depends on previous state
- Multiple related state transitions are involved
Itβs similar to Redux-style reducers but scoped within a component.
π§ͺ Syntax:
const [state, dispatch] = useReducer(reducerFunction, initialState);
import { useReducer } from 'react';
const initialState = { count: 0 };
// Reducer function
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 Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
β
When to Use useReducer()
| Situation | Use useReducer()? |
|---|---|
| Complex state with multiple actions | β Yes |
| State derived from previous state | β Yes |
| Simple toggle or single field state | β useState() is simpler |
β
8. useLayoutEffect()
π Description:
useLayoutEffect() is just like useEffect(), but it runs synchronously after all DOM mutations but before the browser repaints.
This means:
- It blocks painting until the effect is complete.
- Useful when you need to measure DOM elements or make visual updates immediately.
π§ͺ Syntax:
useLayoutEffect(() => {
// Do something immediately after DOM update
return () => {
// Cleanup (optional)
};
}, [dependencies]);
β οΈ Difference from useEffect()
| Feature | useEffect() | useLayoutEffect() |
|---|---|---|
| Runs after render? | β Yes (async) | β Yes (sync, before paint) |
| Affects paint? | β No (runs after paint) | β Yes (can block paint) |
| Usage | API calls, timers, cleanup | DOM measurements, animations |
β
9. useImperativeHandle()
π Description:
useImperativeHandle() is a React hook used with forwardRef() to let child components expose specific methods or properties to the parent.
It’s useful when the parent needs to control or call functions inside the child component, but you want to control exactly what’s exposed β instead of giving full access to the DOM or internal state.
π§ͺ Syntax:
useImperativeHandle(ref, () => ({
method1,
method2,
// only expose what you want
}), [dependencies]);
βοΈ Example: Expose a Focus Method from a Child Input
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
// 1. Child component with forwardRef
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
// 2. Expose methods to parent
useImperativeHandle(ref, () => ({
focusInput: () => inputRef.current.focus()
}));
return <input ref={inputRef} type="text" />;
});
// 3. Parent component
function Parent() {
const inputRef = useRef();
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focusInput()}>
Focus from Parent
</button>
</div>
);
}
π§ When to Use useImperativeHandle():
| Use Case | β Use it? |
|---|---|
| Parent needs to control child’s function | β Yes |
| Custom modal, input, form handling | β Yes |
| You want to expose only specific methods | β Yes |
| Simple DOM access only | β Use useRef() directly |
β
10. useId() (React 18+)
π Description:
useId() is a React 18+ hook used to generate unique, stable IDs for accessibility and SSR (Server-Side Rendering) support.
It’s helpful for linking form elements like <label> and <input> β especially when:
- You need unique IDs in multiple instances of a component
- You’re using SSR and want to prevent ID mismatches
π§ͺ Syntax:
const id = useId();
π§ Example: Accessible Form Input
import { useId } from 'react';
function NameInput() {
const id = useId(); // unique ID for this component instance
return (
<div>
<label htmlFor={id}>Name:</label>
<input id={id} type="text" />
</div>
);
}
Do not call useId() inside .map() β thatβs invalid in React. Yes, you can define multiple unique IDs in a component β but you must not define them inside loops using hooks like useId().
β
11. useDebugValue()
useDebugValue() is a React Hook used only for debugging custom hooks in React DevTools.
useDebugValue(isOnline ? 'Online' : 'Offline');
In React DevTools, this hook will now display:
π’ useOnlineStatus: "Online" or π΄ "Offline"
β What Are Custom Hooks?
A custom hook is a JavaScript function whose name starts with use, and it can use other hooks internally (like useState, useEffect, etc.).
π― Goal: Extract and reuse logic across multiple components without duplicating code.
π§± Basic Syntax
function useCustomHook() {
// use other hooks like useState, useEffect
return something;
}
π Example 1: useCounter (Simple Reusable Hook)
import { useState } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
β Use it in any component:
function CounterComponent() {
const { count, increment, decrement, reset } = useCounter(5);
return (
<div>
<p>{count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>β</button>
<button onClick={reset}>Reset</button>
</div>
);
}
β Benefits of Custom Hooks
| Benefit | Description |
|---|---|
| β» Reusability | Share logic between components easily |
| π¦ Separation of Concerns | UI and logic live separately |
| π Clean Code | Removes duplication, makes components smaller |
| π§ͺ Easy Testing | Hook logic can be tested independently |
βοΈ React Hook Rules
- Only call hooks at the top level
- Only call hooks from React functions
π What is Concurrent React?
Concurrent rendering allows React to interrupt long rendering tasks and give priority to more important updates (like typing or animations).
β‘οΈ React 18 enables this with features like
startTransition,useTransition,useDeferredValue, and automatic batching.
β
1. startTransition() β Mark Updates as Non-Urgent
π Description:
Used to mark state updates that are non-urgent, so React can delay them if needed β like updating search results while the user types.
π§ͺ Example:
import { useState, startTransition } from 'react';
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleChange(e) {
const value = e.target.value;
setQuery(value);
// Mark as low priority
startTransition(() => {
const filtered = slowSearch(value); // assume slow function
setResults(filtered);
});
}
return (
<>
<input value={query} onChange={handleChange} />
<ul>{results.map(r => <li key={r}>{r}</li>)}</ul>
</>
);
}
β
2. useTransition() β User-Friendly Way to Handle Transitions
π Description:
A hook version of startTransition() that also tells you if the transition is pending (still in progress).
π§ͺ Example:
import { useState, useTransition } from 'react';
function ProductFilter({ products }) {
const [input, setInput] = useState('');
const [filtered, setFiltered] = useState(products);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setInput(value);
startTransition(() => {
const result = products.filter(p => p.includes(value));
setFiltered(result);
});
};
return (
<div>
<input value={input} onChange={handleChange} />
{isPending && <p>Loading...</p>}
<ul>{filtered.map(p => <li key={p}>{p}</li>)}</ul>
</div>
);
}
β
3. useDeferredValue() β Defer Heavy Calculations
π Description:
Defer updates to less important values so they update after more urgent ones (like input typing).
π§ͺ Example:
import { useState, useDeferredValue } from 'react';
function SearchList({ data }) {
const [input, setInput] = useState('');
const deferredInput = useDeferredValue(input); // updates slower
const filtered = data.filter(item =>
item.toLowerCase().includes(deferredInput.toLowerCase())
);
return (
<>
<input value={input} onChange={e => setInput(e.target.value)} />
<ul>{filtered.map(item => <li key={item}>{item}</li>)}</ul>
</>
);
}
React Router v6 Hooks – useNavigate(), useLocaton(), useParams()
useNavigate() replaces history.push() and history.replace(). In older versions (v5 and below), you used history.push directly.
import { useNavigate } from 'react-router-dom';
const Component = () => {
const navigate = useNavigate();
// Push (adds to history stack)
const goToProfile = () => {
navigate('/profile');
};
// Replace (replaces current entry in history stack)
const replaceWithHome = () => {
navigate('/', { replace: true });
};
return (
<>
<button onClick={goToProfile}>Go to Profile</button>
<button onClick={replaceWithHome}>Replace with Home</button>
</>
);
};
π§ push vs replace
| Method | Behavior |
|---|---|
navigate(path) | Adds a new entry to the browser history stack |
navigate(path, { replace: true }) | Replaces the current entry (like redirect) |
π¦ Passing Parameters with push / navigate
There are two common ways to pass data:
1. Using URL Params (Path Params)
navigate(`/user/${userId}`);
In route:
<Route path="/user/:id" element={<User />} />
In component:
const { id } = useParams();
2. Using state (Hidden / Non-URL)
tnavigate('/profile', { state: { name: 'Ved', role: 'admin' } });
And then access it in the target component:
import { useLocation } from 'react-router-dom';
const Profile = () => {
const location = useLocation();
const { name, role } = location.state || {};
return (
<div>
<h1>{name}'s Profile</h1>
<p>Role: {role}</p>
</div>
);
};
β
Advantage: cleaner URL, good for passing complex data
β Data is lost on full page reload (no persistence)
β 4. Automatic Batching
π Description:
In React 18+, multiple state updates inside:
- Timeouts
- Promises
- Event handlers
…are batched together to prevent unnecessary re-renders.
π§ͺ Before vs After:
setCount1(c => c + 1);
setCount2(c => c + 1);
β In React 18+, this is automatically batched β only one render occurs.
π Summary Table
| Feature | Purpose |
|---|---|
startTransition | Mark updates as low priority |
useTransition | Hook to manage transitions + pending state |
useDeferredValue | Delay updates for performance |
| Automatic Batching | Group multiple updates β fewer renders |
Suspense | Async UI handling |
π What is the React Profiler?
The React Profiler is a built-in tool in the React Developer Tools extension. It helps you:
- Measure how often components render
- See what caused re-renders
- Identify slow renders and unnecessary updates
π§ What is Redux Thunk?
A thunk is a function that wraps an expression to delay its evaluation.
In Redux, a thunk is a function that returns another function β this lets you delay dispatching actions or dispatch asynchronous actions (like API calls).
π¬ In simple terms:
It allows action creators to return a function instead of an action object.
π Why Use Redux Thunk?
Reduxβs dispatch() only handles plain objects.
To handle API calls, delayed actions, or any side-effects, you need middleware like redux-thunk.
π¦ How to Set Up Thunk
1. Install:
npm install redux-thunk
2. Apply it to the store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
π Thunk Action Creator Syntax
const fetchUsers = () => {
return function (dispatch) {
dispatch({ type: 'FETCH_USERS_REQUEST' });
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(data =>
dispatch({ type: 'FETCH_USERS_SUCCESS', payload: data })
)
.catch(error =>
dispatch({ type: 'FETCH_USERS_FAILURE', payload: error })
);
};
};
β This is a thunk: returns a function instead of a plain object
π Using Thunk in Component
import { useDispatch, useSelector } from 'react-redux';
import { fetchUsers } from './actions/userActions';
function UserList() {
const dispatch = useDispatch();
const { users, loading } = useSelector(state => state.user);
useEffect(() => {
dispatch(fetchUsers()); // Thunk is triggered
}, []);
if (loading) return <p>Loading...</p>;
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}
β Benefits of Redux Thunk
| Feature | Description |
|---|---|
| Async Support | Handles API calls, timeouts, etc. |
| Logic outside components | Keeps components cleaner |
| Fine-grained control | Dispatch multiple actions manually |
| Easy to debug | Follows normal function pattern |
β Downsides
- Can lead to callback hell for complex flows
- Not as structured as
redux-sagaorredux-observable - Logic can get spread out if not modularized well
Complete Redux setup example
complete Redux setup example with a modular folder structure for a store that manages multiple objects (like user, posts, etc.)
π Folder Structure:
/redux
/store.js
/reducers
index.js
userReducer.js
postReducer.js
/actions
userActions.js
postActions.js
1οΈβ£ redux/store.js β Store Configuration
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
π Explanation:
createStorecreates the Redux storethunkallows async actions- Uses
rootReducer(combined reducers)
2οΈβ£ redux/reducers/index.js β Combine Reducers
import { combineReducers } from 'redux';
import userReducer from './userReducer';
import postReducer from './postReducer';
const rootReducer = combineReducers({
user: userReducer,
post: postReducer
});
export default rootReducer;
π Explanation:
- Combines
userReducerandpostReducer - The final Redux state will be:
{
user: {...},
post: {...}
}
3οΈβ£ redux/reducers/userReducer.js
const initialState = {
users: [],
loading: false,
error: null
};
export default function userReducer(state = initialState, action) {
switch (action.type) {
case 'FETCH_USERS_REQUEST':
return { ...state, loading: true };
case 'FETCH_USERS_SUCCESS':
return { users: action.payload, loading: false, error: null };
case 'FETCH_USERS_FAILURE':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
}
4οΈβ£ redux/reducers/postReducer.js
const initialState = {
posts: [],
loading: false,
error: null
};
export default function postReducer(state = initialState, action) {
switch (action.type) {
case 'FETCH_POSTS_REQUEST':
return { ...state, loading: true };
case 'FETCH_POSTS_SUCCESS':
return { posts: action.payload, loading: false, error: null };
case 'FETCH_POSTS_FAILURE':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
}
5οΈβ£ redux/actions/userActions.js
export const fetchUsers = () => {
return async (dispatch) => {
dispatch({ type: 'FETCH_USERS_REQUEST' });
try {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await res.json();
dispatch({ type: 'FETCH_USERS_SUCCESS', payload: data });
} catch (err) {
dispatch({ type: 'FETCH_USERS_FAILURE', payload: err.message });
}
};
};
6οΈβ£ redux/actions/postActions.js
export const fetchPosts = () => {
return async (dispatch) => {
dispatch({ type: 'FETCH_POSTS_REQUEST' });
try {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await res.json();
dispatch({ type: 'FETCH_POSTS_SUCCESS', payload: data });
} catch (err) {
dispatch({ type: 'FETCH_POSTS_FAILURE', payload: err.message });
}
};
};
β
Usage in React Component (e.g., App.js)
import React, { useEffect } from 'react';JavaScript object destructuring
import { useSelector, useDispatch } from 'react-redux';
import { fetchUsers } from './redux/actions/userActions';
import { fetchPosts } from './redux/actions/postActions';
function App() {
const dispatch = useDispatch();
const { users, loading: usersLoading } = useSelector(state => state.user); //
const { posts, loading: postsLoading } = useSelector(state => state.post);
useEffect(() => {
dispatch(fetchUsers());
dispatch(fetchPosts());
}, [dispatch]);
return (
<div>
<h1>Users</h1>
{usersLoading ? <p>Loading Users...</p> : users.map(u => <p key={u.id}>{u.name}</p>)}
<h1>Posts</h1>
{postsLoading ? <p>Loading Posts...</p> : posts.map(p => <p key={p.id}>{p.title}</p>)}
</div>
);
}
export default App;
β
Conditional Dispatch in Redux (React + Redux)
Normal method to check
const handleLoad = () => {
if (users.length === 0 && !loading) {
dispatch(fetchUsers()); // dispatch only if not already loaded
}
};
Inside a Thunk (Action Creator)
export const fetchUsersIfNeeded = () => {
return (dispatch, getState) => {
const { users, loading } = getState().user;
if (users.length === 0 && !loading) {
dispatch({ type: 'FETCH_USERS_REQUEST' });
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(data => dispatch({ type: 'FETCH_USERS_SUCCESS', payload: data }))
.catch(err => dispatch({ type: 'FETCH_USERS_FAILURE', payload: err }));
}
};
};
// usage
dispatch(fetchUsersIfNeeded());
β
children Prop in React
It is a prop {this.props.children} that allow you to pass components as data to other components.
<Wrapper>
<h1>Hello, Ved!</h1>. // whatever place here is padded as prop.childern
</Wrapper>
//wrapper component
const Wrapper = ({ children }) => {
return <div className="box">{children}</div>;
};
//ouput
<div class="box">
<h1>Hello, Ved!</h1>
</div>
π Pro Tip: children is just a prop
You can even pass it manually:
<Wrapper children={<p>Manual way</p>} />
π Common Use Cases
| Use Case | Example |
|---|---|
| Layout Components | <Card>{content}</Card> |
| Modal/Dialog wrappers | <Modal>{form}</Modal> |
| Slot-style flexibility | Like Vueβs <slot> equivalent |
| Libraries like React Router | <Route>{component}</Route> |
π React Reconciliation β Explained Simply
Reconciliation is the process by which React updates the DOM efficiently when your appβs state or props change.
π What Happens During Reconciliation?
When a componentβs state or props change:
- React re-renders the component to produce a new virtual DOM tree.
- It compares this new virtual DOM with the previous one using a diffing algorithm.
- React calculates the minimal set of changes needed to update the actual real DOM.
- It applies only the necessary DOM updates (not a full re-render).
β
What is React.lazy()?
React.lazy() is a function that enables lazy loading of components β meaning the component is only loaded when itβs needed.
β
Why Use React.lazy()?
| Benefit | Description |
|---|---|
| π’ Lazy load components | Load them only when needed |
| π¦ Reduce bundle size | Initial JS bundle is smaller |
| β‘ Faster page load | Great for large apps or routes |
π§ Syntax:
const MyComponent = React.lazy(() => import('./MyComponent'));
- This will dynamically load
MyComponentonly when itβs rendered. - You must wrap it with
<Suspense>to handle loading fallback.
// App.js
import React, { Suspense } from 'react';
const LazyAbout = React.lazy(() => import('./About'));
function App() {
return (
<div>
<h1>My App</h1>
<Suspense fallback={<div>Loading...</div>}>
<LazyAbout />
</Suspense>
</div>
);
}
export default App;
π§ Important Notes
- β
Must use inside a
<Suspense>wrapper - β οΈ Works only for default exports (
export default) - π§© Works well with React Router and route-based code splitting
π Route-Based Lazy Loading with React Router
import { BrowserRouter, Route, Routes } from 'react-router-dom';
const LazyHome = React.lazy(() => import('./pages/Home'));
const LazyAbout = React.lazy(() => import('./pages/About'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<p>Loading...</p>}>
<Routes>
<Route path="/" element={<LazyHome />} />
<Route path="/about" element={<LazyAbout />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
π React Portal β Render Outside the DOM Hierarchy
A Portal in React lets you render a component outside its parent DOM hierarchy, typically at the root level of the HTML document (<body>). This is especially useful for modals, tooltips, dropdowns, etc.
π Why Use Portals?
- You want a component to visually break out of its parent container.
- Avoid CSS issues like
overflow: hiddenorz-indexclashes. - Great for overlay UIs, modals, and global components.
π§± Syntax
ReactDOM.createPortal(child, container)
child: The JSX/React element to render.container: A DOM node outside the React root (often inpublic/index.html).
π‘οΈ Error Boundaries in React
Error Boundaries are special React components that catch JavaScript errors in their child component tree during rendering, lifecycle methods, and constructors, and display a fallback UI instead of crashing the whole app.
π¨ Why Use Error Boundaries?
| Benefit | Description |
|---|---|
| π§― Prevent crashes | App wonβt crash entirely on UI errors |
| π§© Show fallback UI | Show custom UI like “Something went wrong” |
| π Debugging support | Can log error details to monitoring services |
β οΈ What They Catch (and Donβt)
β Catches:
- Rendering errors
- Lifecycle errors
- Constructor errors
β Doesn’t catch:
- Event handler errors (you handle those with
try/catch) - Async code (e.g.
setTimeout) - Server-side rendering errors
π§± Class-Based Error Boundary Example
import React, { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state to show fallback UI
return { hasError: true };
}
componentDidCatch(error, info) {
// Log error to monitoring service
console.error("Caught error:", error, info);
}
render() {
if (this.state.hasError) {
return <h2>Something went wrong.</h2>;
}
return this.props.children;
}
}
export default ErrorBoundary;
π§ͺ Usage
import ErrorBoundary from './ErrorBoundary';
import BuggyComponent from './BuggyComponent';
function App() {
return (
<div>
<h1>My App</h1>
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
</div>
);
}
If BuggyComponent throws an error, ErrorBoundary will catch it and render the fallback.
π₯ Buggy Component Example
function BuggyComponent() {
throw new Error("Oops! Something broke.");
return <div>This won't render</div>;
}
βοΈ Multiple Error Boundaries
You can wrap parts of your app separately:
<ErrorBoundary>
<ComponentA />
</ErrorBoundary>
<ErrorBoundary>
<ComponentB />
</ErrorBoundary>
Useful when different areas of the app should show different fallbacks or recover independently.
β Can We Use Error Boundary in Functional Components?
Not directly, because:
getDerivedStateFromErrorandcomponentDidCatchare lifecycle methods that only work in class components.
π Alternative: Use a custom hook with try/catch in event handlers, or a wrapper like react-error-boundary if you want to use functional style.
π§° Popular Libraries
react-error-boundaryβ Provides a functional way to handle errors with hooks
β Can use server side rendering in react without next.js ?
Yes β
, you can use server-side rendering (SSR) in React without Next.js β by using Node.js + Express (or any backend) and manually setting up SSR using ReactDOMServer.
π What You Need
- React (your components)
react-dom/server(ReactDOMServer)- A Node.js server (like Express)
- A bundler (Webpack, Vite, etc. β optional for client-side hydration)
/ssr-app
/public
index.html
/src
App.js
client.js
server.js
πΉ 1. src/App.js β React Component
import React from 'react';
const App = () => {
return <h1>Hello from Server-Side Rendered React!</h1>;
};
export default App;
πΉ 2. src/server.js β Express Server with SSR
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App.js';
const app = express();
const PORT = 3000;
// Serve static files like client.js
app.use(express.static('public'));
app.get('*', (req, res) => {
const appHtml = ReactDOMServer.renderToString(<App />);
const html = `
<!DOCTYPE html>
<html>
<head>
<title>React SSR</title>
</head>
<body>
<div id="root">${appHtml}</div>
<script src="/client.js"></script> <!-- hydration -->
</body>
</html>
`;
res.send(html);
});
app.listen(PORT, () => {
console.log(`π Server running at http://localhost:${PORT}`);
});
πΉ 3. src/client.js β Hydrate on Client Side
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.hydrateRoot(document.getElementById('root'), <App />);
π§ 4. Tooling
- Use Webpack or Vite to build
client.jsand output it to/public. - Transpile your JSX/ES6 server code with Babel (or use
esm,ts-node, etc.)
β Output:
- The HTML is generated on the server and sent to the client.
- Then, the client-side JS takes over (hydrates) the React app.
π Advantages vs Next.js
| Feature | Without Next.js (manual) | Next.js |
|---|---|---|
| Setup | Manual | Auto-configured |
| Routing | Manual (React Router) | Built-in |
| SSR | Yes, with ReactDOMServer | Out of the box |
| SEO friendly | β Yes | β Yes |
| Fine-grained control | β Full server control | Less flexibility (opinionated) |
βοΈ create-react-app (CRA) β Quick Start to React Projects
create-react-app is the official tool provided by the React team to bootstrap a fully configured React application β without needing to manually set up Webpack, Babel, ESLint, etc.
β What Does CRA Give You?
| Feature | Description |
|---|---|
| π¦ Webpack + Babel | Bundling & transpiling already configured |
| π§ͺ Testing setup | Includes Jest and React Testing Library |
| π CSS/SCSS support | Works out-of-the-box with CSS & preprocessors |
| πΌ Asset handling | Supports images, fonts, and more via import |
| π ESLint + Formatting | Enforced linting + Prettier-style code formatting |
| β‘ Dev server | Comes with live-reloading dev server |
| π Production build | Generates optimized production-ready bundles |
π§± Create a New App
npx create-react-app my-app
cd my-app
npm start
This will create a folder named
my-appwith all required files and dependencies.
π Common Scripts
npm start # Run development server
npm run build # Create optimized production build
npm test # Run unit tests
npm run eject # Remove CRA abstraction (advanced use).
//π Use eject only if you need full control over config (no going back).
π§ Smart vs Dumb Components in React
| Feature | Smart Component | Dumb Component |
|---|---|---|
| Purpose | Logic, data handling | UI rendering only |
| State | β Yes | β Usually stateless |
| Side effects (API) | β Yes | β No |
| Reusable | β Less | β Highly reusable |
| Examples | Containers, Pages | Buttons, Cards, Lists |
| Knows about Redux/Context | β Often | β Never |
β Good Practice
- Separate concerns: logic in smart components, UI in dumb components.
- Keeps code clean, testable, and scalable.
π value vs defaultValue in React
Both value and defaultValue are used in form elements like <input>, <textarea>, and <select>, but they behave very differently depending on controlled vs uncontrolled components.
β
value (Controlled Component)
- The input value is controlled by React state
- You must use
onChangeto update the value - React has full control over the input
π§ Example:
const [name, setName] = useState('');
<input value={name} onChange={e => setName(e.target.value)} />
React updates the value based on
state. Input changes won’t work unless you provide anonChangehandler.
β οΈ defaultValue (Uncontrolled Component)
- Sets the initial value only once on first render
- After that, the value is managed by the DOM itself, not React
- Does not require
onChange, but you can still use it
π§ Example:
<input defaultValue="Ved" />
Acts like a regular HTML input with an initial value. React wonβt track or update it afterward.
β React Testing β Tools, Best Practices & Writing Test Cases
Testing in React ensures your components behave as expected and helps catch bugs early.
π Best Tools for Testing React Apps
| Tool | Purpose | Recommended |
|---|---|---|
| Jest | Test runner + assertion library | β Yes |
| React Testing Library (RTL) | Test React components via UI behavior | β Yes |
| Enzyme (legacy) | Component testing using internal structure | β Deprecated |
| Cypress | End-to-end (E2E) UI testing | β Yes |
| Vitest | Alternative to Jest (faster, for Vite) | β‘ Optional |
π§° Recommended Setup (most common)
- Jest + React Testing Library (RTL)
CRA (create-react-app) comes with Jest pre-installed.
To add RTL (if needed):
npm install --save-dev @testing-library/react @testing-library/jest-dom
β Types of Tests
| Type | Purpose | Tools |
|---|---|---|
| Unit Test | Test isolated functions/components | Jest + RTL |
| Integration | Test multiple components together | Jest + RTL |
| E2E Test | Test full app in browser | Cypress |
π§ͺ Writing a Simple Test Case
πΉ Component: Greeting.js
const Greeting = ({ name }) => {
return <h1>Hello, {name || 'Guest'}!</h1>;
};
export default Greeting;
πΈ Test: Greeting.test.js
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';
test('renders greeting with name', () => {
render(<Greeting name="Ved" />);
expect(screen.getByText('Hello, Ved!')).toBeInTheDocument();
});
test('renders greeting with default', () => {
render(<Greeting />);
expect(screen.getByText('Hello, Guest!')).toBeInTheDocument();
});
π§ Common React Test Utilities
| Function | Purpose |
|---|---|
render() | Render a component into virtual DOM |
screen.getByText() | Query rendered DOM |
fireEvent.click() | Simulate user events |
expect(...).toBe... | Assert result |
waitFor() | Wait for async updates |
π₯ Testing Form Inputs
import { render, screen, fireEvent } from '@testing-library/react';
import Form from './Form';
test('updates input value', () => {
render(<Form />);
const input = screen.getByPlaceholderText('Enter name');
fireEvent.change(input, { target: { value: 'Ved' } });
expect(input.value).toBe('Ved');
});
π¦ Example Folder Structure
/src
/components
Greeting.js
Greeting.test.js
Form.js
Form.test.js
π Running Tests
npm test
This will run in watch mode (re-runs affected tests on save).
π‘ Best Practices
β
Use data-testid or accessible labels (avoid className selectors)
β
Test behavior, not internal implementation
β
Keep tests fast and isolated
β
Write both positive and negative cases
β
Use jest.fn() to mock functions
β Bonus: When to Use Cypress?
Use Cypress when you want to test:
- Navigation
- Actual browser behavior
- Integration between pages/components
π§ͺ Jest Hooks/Lifecycle: Setup & Teardown β beforeEach, afterEach, beforeAll, afterAll
Jest provides global lifecycle hooks to run code before and after tests β perfect for setup, teardown, or shared logic.
describe('Math tests', () => {
beforeAll(() => {
console.log('π§ Setup before all tests');
});
afterAll(() => {
console.log('π§Ή Cleanup after all tests');
});
beforeEach(() => {
console.log('π¦ Setup before each test');
});
afterEach(() => {
console.log('π§½ Cleanup after each test');
});
test('adds numbers', () => {
expect(1 + 2).toBe(3);
});
test('multiplies numbers', () => {
expect(2 * 2).toBe(4);
});
});
Jasmine: it s also a testing framework (BDD) but less common, and not good as compare to Jest.
π Callback vs π Promise vs β‘ Async/Await in JavaScript
π§© 1. Callback (Legacy before ES6)
A function passed as an argument to another function to be executed later.
π Example:
function greetUser(name, callback) {
console.log(`Hello, ${name}`);
callback();
}
function sayBye() {
console.log('Goodbye!');
}
greetUser('Ved', sayBye);
β οΈ Drawback:
- Leads to callback hell when nesting too deeply
- Difficult to read, debug, and manage
π 2. Promise (ES6)
A cleaner way to handle async operations, providing
.then()and.catch()methods.
π Example:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data loaded');
}, 1000);
});
}
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));
β Benefit:
- Chainable, cleaner than nested callbacks
- Easier to handle errors using
.catch()
β
A Promise can return:
| Return Type | What It Means |
|---|---|
resolve(value) | The operation succeeded, and value is the result |
reject(error) | The operation failed, and error is the reason |
return another promise | The outer promise will adopt the inner promise’s state |
| Nothing | It will be treated as undefined return when resolved. rresolve(); // Same as resolve(undefined) |
β‘ 3. Async/Await (ES8 (ES2017))
A syntax sugar built on top of Promises that makes async code look synchronous.
π Example:
async function loadData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error(error);
}
}
loadData();
β Benefit:
- Most readable and maintainable
- Use
try/catchfor error handling - Clean sequential logic
π Fetch Multiple URLs in React (or JavaScript)
To fetch multiple URLs in React (or any JS code), you typically use:
Promise.all()β to wait for all requests to finishasync/awaitβ to handle them cleanly
β Basic Example β Fetch Multiple APIs
const fetchMultipleData = async () => {
const urls = [
'https://api.example.com/users',
'https://api.example.com/posts',
'https://api.example.com/comments',
];
try {
const responses = await Promise.all(urls.map(url => fetch(url)));
const data = await Promise.all(responses.map(res => res.json()));
console.log(data); // [usersData, postsData, commentsData]
} catch (err) {
console.error('Error fetching:', err);
}
};
π§© React Component Example
import React, { useEffect, useState } from 'react';
const MultiFetch = () => {
const [users, setUsers] = useState([]);
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchData = async () => {
const [userRes, postRes] = await Promise.all([
fetch('https://jsonplaceholder.typicode.com/users'),
fetch('https://jsonplaceholder.typicode.com/posts')
]);
const [userData, postData] = await Promise.all([
userRes.json(),
postRes.json()
]);
setUsers(userData);
setPosts(postData);
};
fetchData();
}, []);
return (
<div>
<h2>Users: {users.length}</h2>
<h2>Posts: {posts.length}</h2>
</div>
);
};
export default MultiFetch;
β Handle Cascading failure case
When fetching multiple URLs, it’s crucial to handle both scenarios:
- β All requests succeed
- β One or more requests fail
β
Option 1: Use Promise.all β Fast but fails on any error
try {
const responses = await Promise.all([
fetch('/api/one'),
fetch('/api/two'),
fetch('/api/three'),
]);
const data = await Promise.all(responses.map(res => res.json()));
console.log('All passed β
', data);
} catch (error) {
console.error('At least one request failed β:', error);
}
β Limitation:
- If even one request fails (e.g., network error or 500), the entire block fails.
- No partial success.
β
Option 2: Use Promise.allSettled β Safer for mixed results
const urls = ['/api/one', '/api/two', '/api/three'];
const results = await Promise.allSettled(urls.map(url => fetch(url)));
const successful = [];
const failed = [];
for (let i = 0; i < results.length; i++) {
if (results[i].status === 'fulfilled') {
const json = await results[i].value.json();
successful.push({ url: urls[i], data: json });
} else {
failed.push({ url: urls[i], error: results[i].reason });
}
}
if (failed.length === 0) {
console.log('π All requests succeeded', successful);
} else {
console.warn('β οΈ Some requests failed', failed);
console.log('β
Successful responses', successful);
}
π yield in JavaScript
π§ What is yield?
yield is a special keyword used inside generator functions to pause the function execution and return a value.
It allows building iterators, lazy sequences, and asynchronous flows.
π§ Syntax
function* generatorName() {
yield value1;
yield value2;
}
function*declares a generator functionyieldpauses execution and returns a value- Calling the generator returns an iterator object
β Basic Example
function* colors() {
yield 'red';
yield 'green';
yield 'blue';
}
const gen = colors();
console.log(gen.next()); // { value: 'red', done: false }
console.log(gen.next()); // { value: 'green', done: false }
console.log(gen.next()); // { value: 'blue', done: false }
console.log(gen.next()); // { value: undefined, done: true }
π§© How yield Works
| Concept | Explanation |
|---|---|
yield | Pauses the generator and returns a value |
next() | Resumes execution from where it was paused |
Multiple yields | Acts like a step-by-step iterator |
| Can accept input | next(value) passes data back into the generator |
π₯ yield with Input (Two-Way Communication)
function* greet() {
const name = yield "What is your name?";
yield `Hello, ${name}!`;
}
const g = greet();
console.log(g.next().value); // "What is your name?"
console.log(g.next('Ved').value); // "Hello, Ved!"
Limitation: Yields can only be called directly from the generator function that contain it. It can not be called from the nested function or from callbacks.
β What is Redux Saga in React?
Redux-Saga is a middleware library used in Redux applications to handle side effects like:
- API calls
- Delays or retries
- Accessing browser cache, storage, etc.
Instead of using thunks (functions inside actions), Redux-Saga uses generators and yield to handle async code in a more declarative and testable way.
π§ Why Use Redux-Saga?
| Problem in Redux | How Redux-Saga Helps |
|---|---|
| Redux is synchronous | Saga lets you handle async logic |
| Async code in actions (thunk) becomes messy | Saga uses generator-based workflows |
| Difficult to test thunks | Saga effects (call, put, etc.) are testable |
π§ Basic Redux-Saga Flow
- UI Dispatches an action (
FETCH_USER_REQUEST) - Saga intercepts the action
- Saga runs side-effect (e.g., API call)
- Saga dispatches success or error actions
β Key Concepts in Redux-Saga
| Concept | Description |
|---|---|
saga | A generator function that performs async tasks |
takeEvery | Listens for every matching action and runs a worker saga |
call | Invokes a function (like an API call) and waits for result |
put | Dispatches an action |
takeLatest | Only runs the latest call (cancels older ones) |
π Folder Structure
/redux-saga-example/
β
βββ /src/
β βββ /redux/
β β βββ store.js
β β βββ rootReducer.js
β β βββ rootSaga.js
β β βββ /user/
β β β βββ userActions.js
β β β βββ userReducer.js
β β β βββ userSaga.js
β
βββ /components/
β βββ UserComponent.jsx
β
βββ App.js
βββ index.js
π§© 1. userActions.js
// src/redux/user/userActions.js
export const FETCH_USER_REQUEST = 'FETCH_USER_REQUEST';
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS';
export const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE';
export const fetchUserRequest = (id) => ({ type: FETCH_USER_REQUEST, payload: id });
π§ 2. userReducer.js
// src/redux/user/userReducer.js
import { FETCH_USER_REQUEST, FETCH_USER_SUCCESS, FETCH_USER_FAILURE } from './userActions';
const initialState = {
user: null,
loading: false,
error: null,
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER_REQUEST:
return { ...state, loading: true };
case FETCH_USER_SUCCESS:
return { ...state, user: action.payload, loading: false };
case FETCH_USER_FAILURE:
return { ...state, error: action.error, loading: false };
default:
return state;
}
};
export default userReducer;
βοΈ 3. userSaga.js
// src/redux/user/userSaga.js
import { call, put, takeEvery } from 'redux-saga/effects';
import axios from 'axios';
import { FETCH_USER_REQUEST } from './userActions';
function* fetchUserWorker(action) {
try {
const response = yield call(axios.get, `https://jsonplaceholder.typicode.com/users/${action.payload}`);
yield put({ type: 'FETCH_USER_SUCCESS', payload: response.data });
} catch (e) {
yield put({ type: 'FETCH_USER_FAILURE', error: e.message });
}
}
export default function* userSaga() {
yield takeEvery(FETCH_USER_REQUEST, fetchUserWorker);
}
π 4. rootReducer.js
// src/redux/rootReducer.js
import { combineReducers } from 'redux';
import userReducer from './user/userReducer';
const rootReducer = combineReducers({
user: userReducer,
});
export default rootReducer;
π 5. rootSaga.js
// src/redux/rootSaga.js
import { all } from 'redux-saga/effects';
import userSaga from './user/userSaga';
export default function* rootSaga() {
yield all([userSaga()]);
}
π 6. store.js
// src/redux/store.js
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './rootReducer';
import rootSaga from './rootSaga';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(rootSaga);
export default store;
π§± 7. UserComponent.jsx
// src/components/UserComponent.jsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUserRequest } from '../redux/user/userActions';
const UserComponent = () => {
const dispatch = useDispatch();
const { user, loading, error } = useSelector((state) => state.user);
useEffect(() => {
dispatch(fetchUserRequest(1));
}, [dispatch]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return user ? <div>User: {user.name}</div> : null;
};
export default UserComponent;
π 8. App.js
// src/App.js
import React from 'react';
import UserComponent from './components/UserComponent';
const App = () => <UserComponent />;
export default App;
π 9. index.js
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
β Summary β File Relations
| File | Purpose |
|---|---|
userActions.js | Action constants & creators |
userReducer.js | Handles state changes |
userSaga.js | Watches actions, handles API calls |
rootReducer.js | Combines all reducers |
rootSaga.js | Combines all sagas |
store.js | Configures Redux store & middleware |
UserComponent.jsx | Dispatches action, reads Redux state |
App.js, index.js | Starts the app and wraps with Provider |
π― Common Redux-Saga Effects
| Effect | Purpose |
|---|---|
call | Call a function (e.g., API) and wait for result |
put | Dispatch a Redux action |
take | Wait for a specific action to occur |
takeEvery | Run saga for every matching action |
takeLatest | Run only the latest if rapid actions fired |
fork | Start a non-blocking task |
spawn | Like fork, but detached from parent |
all | Run multiple effects in parallel |
delay | Wait for a specific time (like sleep) |
select | Access the current Redux store state |
cancel | Cancel a running task |
race | Compete multiple effects; only one wins |
π§Ό What is a Pure Function?
A pure function is a function that:
- Always returns the same output for the same input
- Has no side effects (doesnβt modify external state)
A side effect is anything a function does other than returning a result.
π₯ Examples of Side Effects:
| Side Effect Example | What Happens |
|---|---|
| Logging to console | Changes developer console state |
| Modifying a global variable | Alters external data |
| Writing to a file / database | Changes outside world |
| Making an API call | Affects network, outside your app |
| Updating the DOM | Changes the browser UI |
Setting a timer (setTimeout) | Triggers something asynchronously |
π£ What is an Impure Function?
An impure function:
- Might return different outputs for the same input
- Produces side effects
π§© What is Currying?
Currying is the process of transforming a function with multiple arguments into a sequence of functions, each taking a single argument.
It turns:
f(a, b, c)βf(a)(b)(c)
πΈ Normal Function:
function add(a, b) {
return a + b;
}
add(2, 3); // 5
πΈ Curried Version:
function curriedAdd(a) {
return function (b) {
return a + b;
};
}
curriedAdd(2)(3); // 5
π§ Using ES6 Arrow Functions
const curriedMultiply = a => b => a * b;
curriedMultiply(4)(5); // 20
π¦ Webpack + Sass (SCSS) Setup From Scratch
This guide sets up a modern front-end development environment using:
β
Webpack (bundler)
β
Babel (for ES6+)
β
Sass (CSS preprocessor)
β
Dev server with live reloading
π§± 1. π Folder Structure
my-app/
βββ dist/
β βββ index.html
βββ src/
β βββ index.js
β βββ style.scss
βββ package.json
βββ webpack.config.js
βββ .babelrc
βοΈ 2. π§ Initialize Project
mkdir my-app && cd my-app
npm init -y
π§© 3. π¦ Install Dependencies
β€ Webpack & Babel
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save-dev @babel/core @babel/preset-env babel-loader
β€ Sass & Loaders
npm install --save-dev sass sass-loader css-loader style-loader
π§ 4. βοΈ Webpack Config
πΈ webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
devServer: {
static: './dist',
hot: true,
port: 3000,
open: true,
},
module: {
rules: [
{
test: /\.js$/, // JS
exclude: /node_modules/,
use: 'babel-loader',
},
{
test: /\.scss$/, // Sass
use: ['style-loader', 'css-loader', 'sass-loader'],
},
],
},
};
π§ 5. π§ Babel Configuration
πΈ .babelrc
{
"presets": ["@babel/preset-env"]
}
βοΈ 6. Create Files
πΈ src/index.js
import './style.scss';
console.log('Webpack + Sass working!');
πΈ src/style.scss
$color: #2ecc71;
body {
background: $color;
font-family: Arial, sans-serif;
padding: 2rem;
color: white;
}
πΈ dist/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Webpack Sass Setup</title>
</head>
<body>
<h1>Hello Webpack + Sass!</h1>
<script src="bundle.js"></script>
</body>
</html>
π§ͺ 7. Add Scripts in package.json
"scripts": {
"start": "webpack serve",
"build": "webpack"
}
π 8. Start Development Server
npm start
Visit π http://localhost:3000
βοΈ Common React Patterns
- Container & Presentational
- Separate logic (container) from UI (presentational)
- Higher-Order Component (HOC)
- Wrap a component to add extra logic (e.g.,
withAuth(Component))
- Wrap a component to add extra logic (e.g.,
- Render Props
- Share logic using a
renderorchildrenfunction
- Share logic using a
- Controlled vs Uncontrolled Components
- Controlled: uses React state
- Uncontrolled: uses refs
- Compound Components
- Components share internal state via context (e.g.,
<Tabs>with<Tabs.Tab>)
- Components share internal state via context (e.g.,
- Custom Hooks
- Reusable logic using
useState,useEffect, etc.
- Reusable logic using
- Context API
- Avoid prop drilling; share global data like theme/auth
- State Reducer Pattern
- Let parent override child behavior via reducer
- Children-as-a-Prop
- Pass JSX in
childrenfor flexible layouts
- Pass JSX in
- Functional State Update
setCount(prev => prev + 1)to update based on previous state
βοΈ React API (REST) vs GraphQL API
| Feature | REST API (React uses fetch/axios) | GraphQL API |
|---|---|---|
| Request Type | Multiple endpoints (e.g., /users, /posts) | Single endpoint (/graphql) |
| Data Fetching | Fixed structure (server decides) | Flexible (client decides what to get) |
| Over-fetching | Yes (gets extra data) | β Avoided (fetch only needed fields) |
| Under-fetching | May need multiple requests | β Avoided (combine in one query) |
| Versioning | Needs new versions (v1, v2) | No versioning needed |
| Batch Requests | Needs custom handling or parallel fetch | Built-in with nested queries |
| Errors | Per request/endpoint | Field-level error support |
| Tools | Fetch, Axios, React Query | Apollo Client, Relay |
| Caching | Manual or with tools (React Query) | Apollo has built-in cache |
| Schema | Optional/open | Strongly typed schema |
π§ Example
β REST in React
useEffect(() => {
fetch('/api/users/1')
.then(res => res.json())
.then(data => setUser(data));
}, []);
β GraphQL in React (Apollo)
const GET_USER = gql`
query GetUser {
user(id: 1) {
name
email
}
}
`;
const { data, loading } = useQuery(GET_USER);
π Normal Function vs Arrow Function
| Feature | Normal Function | Arrow Function |
|---|---|---|
| Syntax | function name() {} | const name = () => {} |
this Binding | Dynamic (this depends on how it’s called) | Lexical (this is inherited from parent scope) |
| Arguments Object | β
Has arguments object | β No arguments object |
Constructors (new) | β Can be used as constructor | β Cannot be used with new |
| Methods in Class | β Common for defining methods | β Not recommended for methods |
| Implicit Return | β Must use return keyword | β
Auto-return if one expression (no {}) |
| Hoisting | β Hoisted | β Not hoisted |
β Example Comparison
πΉ Normal Function
function add(a, b) {
return a + b;
}
πΉ Arrow Function
const add = (a, b) => a + b;
π this Binding Example
const obj = {
count: 10,
normal: function () {
console.log(this.count); // 10 β
},
arrow: () => {
console.log(this.count); // undefined β (lexical this)
},
};
π§ When to Use What?
| Use Normal Function | Use Arrow Function |
|---|---|
| As methods in classes/objects | For short callbacks & functional code |
When this needs to be dynamically bound | When using lexical this is desired |
When using arguments object | For concise, one-line expressions |
