Blog

  • Error Handling in React

    Overview

    Error handling in React helps prevent the app from crashing and improves user experience.

    React doesn’t catch errors in event handlers or lifecycle methods with normal try-catch. Special mechanisms are required.

    1. Error Boundaries

    Error boundaries are React components that catch JavaScript errors anywhere in their child component tree and display a fallback UI instead of crashing.

    When to use them?

    Wrap risky components: widgets, dashboards, charts, lazy-loaded components

    Example:

    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false };
      }
    
      static getDerivedStateFromError(error) {
        return { hasError: true }; // update UI
      }
    
      componentDidCatch(error, errorInfo) {
        console.error("Logged error:", error, errorInfo);
        // log error to service
      }
    
      render() {
        if (this.state.hasError) {
          return <h1>Something went wrong.</h1>;
        }
        return this.props.children;
      }
    }

    Key Point:

    • Error boundaries only catch errors during rendering, lifecycle methods, and constructors in the component tree.

    2. Handling Errors in Event Handlers

    • Error boundaries don’t catch errors in event handlers.
    • Use try...catch inside the function.
    function Button() {
      const handleClick = () => {
        try {
          // risky logic
        } catch (error) {
          console.error("Caught in event:", error);
        }
      };
    
      return <button onClick={handleClick}>Click</button>;
    }

    Key Point:

    • Always use try...catch in event handlers and async functions.

    3. Handling Async Errors

    async function fetchData() {
      try {
        const response = await fetch('/api/data');
        const json = await response.json();
      } catch (err) {
        console.error("Async error:", err);
      }
    }

    Use try-catch for:

    • fetch() / API calls
    • File uploads
    • Database operations

    4. Summary

    What to Know

    • React error handling includes render-time errors, event handler errors, and async operation errors.
    • Use Error Boundaries to catch render and lifecycle errors.
    • Use try...catch for event handlers and async functions.

    Error Types & Handling Methods

    Error TypeHow to Handle
    Render/Lifecycle errorsUse Error Boundaries (getDerivedStateFromError, componentDidCatch)
    Event handler errorsUse try...catch inside the event handler
    Async (API/fetch) errorsUse try...catch inside async functions
    Form/UI-level validationUse local component state to show errors

    Best Practices

    PracticeWhy It Matters
    Use Error BoundariesPrevent the entire app from crashing on render errors
    Add try...catch in eventsCatch and gracefully handle user-triggered logic errors
    Handle async errors properlyAvoid unhandled promise rejections in API calls
    Show fallback UIImprove user experience with error messages or loaders
    Log errors to external servicesHelps with debugging and monitoring in production
  • Rendering & Optimization in React

    Overview

    Rendering in React is the process of converting data (state, props) into UI.

    Optimization ensures better performance and avoids unnecessary re-renders.

    1. Conditional Rendering

    Use if, &&, or ternary ? to render UI based on conditions.

    // Using ternary
    {isLoggedIn ? <Dashboard /> : <Login />}
    
    // Using &&
    {loading && <Spinner />}

    Key Point:

    • Conditional rendering is pure JavaScript logic.
    • Don’t render hidden elements unnecessarily — it still affects performance.

    2. List Rendering & Key

    Render arrays of components using .map().

    const users = ['Alice', 'Bob'];
    
    return (
      <ul>
        {users.map((name, index) => (
          <li key={index}>{name}</li>
        ))}
      </ul>
    );

    Key Point:

    • key helps React track changes in lists.
    • Avoid using the index as the key if the list items can change order.

    3. React.memo

    React.memo is a higher-order component that memoizes a component, preventing re-render unless props change.

    const UserCard = React.memo(function UserCard({ user }) {
      return <div>{user.name}</div>;
    });

    Key Point:

    • Use React.memo when:
      • Component renders often with same props
      • Component is pure and stateless
    • Combine with useCallback / useMemo for best result

    4. useMemo

    useMemo memoizes the result of a computation between renders.

    const sortedData = useMemo(() => sortUsers(users), [users]);

    Key Point:

    • Use for expensive computations.
    • Don’t overuse — it adds complexity.

    5. useCallback

    Memoizes a function to avoid re-creating it on each render.

    const handleClick = useCallback(() => {
      doSomething();
    }, [dependency]);

    Use when:

    • You pass functions to memoized children (React.memo)
    • You want to optimize render of deeply nested components

    6. Lazy Loading & Code Splitting

    Split code and load lazily to improve initial load time.

    const LazyComponent = React.lazy(() => import('./MyComponent'));
    
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>

    Key Point:

    • Requires React.Suspense.
    • Improves performance in large apps.

    Summary Table

    TechniquePurposeWhen to Use
    React.memoPrevents re-render if props haven’t changedFor pure functional components that re-render with the same props
    useMemoMemoizes the result of an expensive calculationWhen you have heavy computations that depend on specific values
    useCallbackMemoizes a function between rendersWhen passing callbacks to memoized child components (React.memo)
    React.lazyLoads a component lazily (code splitting)When you want to reduce initial bundle size
    SuspenseFallback UI while loading lazy componentsUsed with React.lazy or concurrent features
    key in listsHelps React identify which items changed, added, or removedAlways use stable, unique keys (avoid index if list order may change)
    Conditional renderingRenders elements based on conditionsWhen content should change depending on state or props

    Best Practices

    PracticeExplanation
    Avoid unnecessary stateDon’t store values in state that can be derived from props or other state.
    Use stable keysAlways use unique identifiers (like IDs) instead of array index as key.
    Break down large componentsSplit big components into smaller, focused components.
    Memoize wiselyUse React.memo, useMemo, useCallback only when profiling shows benefit.
    Profile performanceUse React DevTools Profiler to detect unnecessary renders.
    Defer non-critical componentsLoad rarely-used components with React.lazy and Suspense.
    Minimize re-rendersAvoid prop changes or state updates that trigger re-renders unnecessarily.

  • Form Handling in React

    Overview

    Form handling is a core part of building interactive web applications. In React, it involves managing user input, validating data, and handling form submission in a controlled or uncontrolled way.

    Controlled vs. Uncontrolled Components

    TypeControlled ComponentUncontrolled Component
    DefinitionReact controls the input via stateDOM holds the value, accessed using ref
    Data sourceuseStateDirect DOM access via useRef
    Suitable forDynamic logic, validationSimple forms (e.g., file upload)
    Re-render on change✅ Yes❌ No
    Common use caseMost formsWhen you want to avoid state for performance

    Controlled Component Example

    function ControlledForm() {
      const [name, setName] = useState('');
    
      const handleSubmit = (e) => {
        e.preventDefault();
        alert(`Name: ${name}`);
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input 
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
          <button type="submit">Submit</button>
        </form>
      );
    }

    Key Point:

    • Controlled components are bound to React state.
    • Each input change triggers a re-render, which gives better control and validation logic.

    Uncontrolled Component Example

    function UncontrolledForm() {
      const nameRef = useRef();
    
      const handleSubmit = (e) => {
        e.preventDefault();
        alert(`Name: ${nameRef.current.value}`);
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input type="text" ref={nameRef} />
          <button type="submit">Submit</button>
        </form>
      );
    }

    Key Point:

    • Uncontrolled components don’t use React state.
    • They are useful for simple forms or performance-sensitive inputs.

    Manual Validation Example

    function ValidatedForm() {
      const [email, setEmail] = useState('');
      const [error, setError] = useState('');
    
      const validate = () => {
        if (!email.includes('@')) {
          setError('Invalid email address');
          return false;
        }
        setError('');
        return true;
      };
    
      const handleSubmit = (e) => {
        e.preventDefault();
        if (validate()) {
          alert('Form submitted!');
        }
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input 
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
          {error && <span style={{ color: 'red' }}>{error}</span>}
          <button type="submit">Submit</button>
        </form>
      );
    }

    Key Point:

    • Manual validation allows flexibility.
    • You must handle error messages and rules explicitly.

    Popular Form Libraries

    LibraryFeaturesWhen to Use
    React Hook FormLightweight, fast, integrates with refComplex forms with minimal re-renders
    FormikDeclarative, handles forms + validation + touched statesDynamic or multi-step forms
    YupSchema-based validation, works well with Formik/RHFCentralized validation logic

    React Hook Form Example

    import { useForm } from 'react-hook-form';
    
    function RHFForm() {
      const { register, handleSubmit } = useForm();
    
      const onSubmit = (data) => {
        console.log(data);
      };
    
      return (
        <form onSubmit={handleSubmit(onSubmit)}>
          <input {...register('username')} />
          <input type="submit" />
        </form>
      );
    }

    Key Point:

    • Inputs are registered via the register() method.
    • You don’t need to handle state or onChange manually.

    Formik + Yup Validation Example

    import { useFormik } from 'formik';
    import * as Yup from 'yup';
    
    const validationSchema = Yup.object({
      email: Yup.string().email('Invalid email').required('Required'),
    });
    
    function FormikForm() {
      const formik = useFormik({
        initialValues: { email: '' },
        validationSchema,
        onSubmit: (values) => console.log(values),
      });
    
      return (
        <form onSubmit={formik.handleSubmit}>
          <input 
            name="email" 
            value={formik.values.email}
            onChange={formik.handleChange}
          />
          {formik.errors.email && <div>{formik.errors.email}</div>}
          <button type="submit">Submit</button>
        </form>
      );
    }

    Key Takeaways

    Best PracticesNotes
    Use controlled componentsThey provide better control over form state and validation
    Use e.preventDefault()Prevent default browser behavior during form submission
    Use useRef only when necessaryIdeal for file inputs or when avoiding re-renders
    Prefer libraries for complex formsRHF and Formik improve scalability and developer productivity
    Use Yup for reusable validationCentralize and reuse validation logic across forms
  • React Routing

    Overview

    Routing is how you navigate between different views/pages in a React Single Page Application (SPA).

    React doesn’t have built-in routing — we commonly use React Router for that.

    1. Installing React Router

    npm install react-router-dom

    2. Core Components

    ComponentPurpose
    <BrowserRouter>Wraps the app to enable routing using browser history
    <Routes>Contains individual route definitions
    <Route path="" element={}>Defines a route for a specific URL
    <Link to="">Navigates without reload
    useNavigate()Programmatic navigation
    useParams()Extracts route parameters
    useLocation()Gives access to current location object

    3. Basic Routing Example

    import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
    
    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>
      );
    }

    4. Nested Routing

    Allows defining routes inside other routes (e.g., /dashboard/profile).

    <Route path="/dashboard" element={<Dashboard />}>
      <Route path="profile" element={<Profile />} />
    </Route>
    import { Outlet } from 'react-router-dom';
    
    function Dashboard() {
      return (
        <>
          <h2>Dashboard</h2>
          <Outlet />
        </>
      );
    }

    5. Dynamic Routing

    Used for URLs with dynamic segments (e.g., /user/:id)

    <Route path="/user/:id" element={<UserDetail />} />
    import { useParams } from 'react-router-dom';
    
    function UserDetail() {
      const { id } = useParams();
      return <h1>User ID: {id}</h1>;
    }

    6. Programmatic Navigation

    import { useNavigate } from 'react-router-dom';
    
    function LoginButton() {
      const navigate = useNavigate();
      return (
        <button onClick={() => navigate('/dashboard')}>
          Go to Dashboard
        </button>
      );
    }

    Key Points

    Always use <Link to=""> for internal navigation

    Extract dynamic segments like :id using useParams()

    Use useNavigate() for programmatic navigation

    Use <Outlet /> in parent component to render nested routes

    Always wrap your app with <BrowserRouter>

  • State Management

    Overview

    State management refers to the process of managing the data (state) of your application across components — how it’s created, updated, and shared.

    There are multiple ways to manage state in React, from simple local state using useState, to complex solutions using libraries like Redux, Zustand, etc.

    1. Lifting State Up

    When two or more child components need to share the same piece of state, it’s best to move that state up to their closest common parent.

    function Parent() {
      const [count, setCount] = useState(0);
      return (
        <>
          <Display count={count} />
          <IncrementButton setCount={setCount} />
        </>
      );
    }

    Key Point:

    Keep state as close as possible to where it’s used, but lift it up when multiple components need access.

    2. Prop Drilling

    Prop drilling occurs when you pass data through many intermediate components that don’t use the data, just to get it to a deeply nested child.

    <Parent>
      <Child1>
        <Child2>
          <Child3 someProp={value} />
        </Child2>
      </Child1>
    </Parent>

    Problem: Code becomes harder to read, maintain, and refactor.

    Solution: Use Context API or state libraries like Redux or Zustand to avoid deep prop drilling.

    3. React Context API

    React Context provides a way to pass data globally to components without prop drilling.

    // Create context
    const ThemeContext = React.createContext();
    
    // Wrap in a Provider
    <ThemeContext.Provider value="dark">
      <App />
    </ThemeContext.Provider>
    
    // Consume context
    const theme = useContext(ThemeContext);

    Key Point:

    Great for global, rarely changing state (e.g., theme, language, auth).

    Not ideal for frequently updated data — can cause unnecessary re-renders.

    4. Comparing State Management Techniques

    ApproachWhen to UseProsCons
    useState (Local)Simple component-level stateEasy, built-inNot shareable across components
    Lifting State UpShare state between siblingsEasy to implementCan cause prop drilling
    Context APIGlobal, rarely-changing stateAvoids prop drillingCan cause performance issues
    ReduxLarge-scale apps with complex data flowCentralized control, time-travel debuggingBoilerplate, harder setup
    Zustand / Recoil / JotaiMid-size apps or when Redux is too muchSimple API, lightweightStill requires external libraries

    5. Popular State Management Libraries

    LibraryKey FeaturesUse Case
    ReduxPredictable, central store, middlewareComplex enterprise apps
    ZustandMinimal setup, no boilerplateSimple to medium apps
    RecoilAtom-based, React tree friendlyData dependencies and interrelations
    JotaiAtomic state, intuitive APIModular shared state
    MobXReactive state, less boilerplateQuick, automatic updates with less config

    Key Takeaways

    Choose the right state management tool based on your app’s complexity and scale.

    Avoid overengineering: don’t use Redux for a simple to-do list.

    For most apps: useState, useContext, and maybe Zustand are sufficient.

  • React Hooks

    Hooks are special functions that let you “hook into” React features like state and lifecycle from function components.

    Introduced in React 16.8, hooks make class components largely unnecessary.

    1. useState

    Used to declare and manage local state inside a function component.

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

    Syntax:

    const [stateVariable, setStateFunction] = useState(initialValue);

    2. useEffect

    Used for side effects: data fetching, subscriptions, timers, DOM manipulation.

    useEffect(() => {
      console.log("Component mounted or updated");
    }, [dependency]);

    Syntax:

    useEffect(callback, [dependencies]);

    Cleanup:

    useEffect(() => {
      const timer = setInterval(...);
      return () => clearInterval(timer);
    }, []);

    3. useContext

    Allows components to consume data from Context without prop drilling.

    const user = useContext(UserContext);

    4. useRef

    Used to reference DOM elements or persist values without triggering re-renders.

    const inputRef = useRef(null);
    <input ref={inputRef} />

    5. useMemo

    Memoizes the result of an expensive computation to avoid unnecessary recalculations.

    const result = useMemo(() => computeExpensiveValue(a, b), [a, b]);

    6. useCallback

    Memoizes a function to prevent unnecessary re-creations across renders.

    const handleClick = useCallback(() => {
      doSomething();
    }, [dependency]);

    7. useReducer (Alternative to useState)

    Used for more complex state logic, similar to Redux-style reducers.

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

    8. Custom Hooks

    A custom hook is a function that uses other hooks. It must start with use.

    function useWindowWidth() {
      const [width, setWidth] = useState(window.innerWidth);
      useEffect(() => {
        const handleResize = () => setWidth(window.innerWidth);
        window.addEventListener('resize', handleResize);
        return () => window.removeEventListener('resize', handleResize);
      }, []);
      return width;
    }

    Summary of Common Hooks

    HookUsed for
    useStateSimple local state
    useEffectSide effects, data fetching
    useContextContext data access
    useRefDOM refs, persistent value
    useMemoMemoize calculation
    useCallbackMemoize functions
    useReducerComplex state logic
    Custom hooksReusable logic
  • Lifecycle in Class Component

    In class components, lifecycle methods are special functions that React automatically calls at different phases of a component’s life.

    Lifecycle Phases

    PhaseExplanationLifecycle methods
    MountingThe component is created and inserted into the DOM.constructor(), render(), componentDidMount()
    UpdatingThe component is re-rendered due to changes in props or state.shouldComponentUpdate(), render(), componentDidUpdate()
    UnmountingThe component is removed from the DOM.componentWillUnmount()
    Error Handling (React 16+)Catching errors in child components.componentDidCatch(), getDerivedStateFromError()
  • Props vs State

    CriteriaPropsState
    What is it?Data from parent componentInternal data of the component
    Who manages it?Managed by parentManaged by itself
    Mutable?No (read-only)Yes, via setState or useState
    Accessible inside component?Yeses
    PurposeConfigure & pass data to componentTrack dynamic internal state
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    function Counter() {
      const [count, setCount] = useState(0);
      return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
    }

    Key point:

    • Props are read-only, used to configure the component from outside.
    • State is used to track internal changes, often in response to user interactions or logic updates.
  • What is a Component?

    A component is a reusable and independent UI block.

    There are 2 types:

    Function Components (Modern)

    Class Components (Legacy)

    // Function Component
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }

    Component Comparison (Function vs Class)

    CriteriaFunction ComponentClass Component
    Concise & easier to write
    Supports Hooks
    Easier to test
    Lifecycle methodsVia useEffect()Built-in in class
    Recommended in modern React
  • What is Virtual DOM?

    Virtual DOM is a lightweight in-memory representation of the real DOM.

    React uses Virtual DOM to:

    • Detect UI changes
    • Compare with previous DOM (diffing algorithm)
    • Efficiently update the real DOM

    Result: better performance, fewer direct DOM manipulations.