Skip to main content

🌟 Mastering React Component Patterns


React is powerful because it gives us flexibility in how we design and structure components. As projects grow, you’ll find yourself needing different patterns to organize code, share logic, and create reusable UIs.

In this blog, we’ll cover 6 important React component patterns with definitions, explanations, and examples:

1. Controlled Components

2. Uncontrolled Components

3. Container vs Presentational Components

4. Higher-Order Components (HOCs)

5. Render Props

6. Compound Components


---

🔹 1. Controlled Components

Definition:
A controlled component is a form element (like <input>, <textarea>, <select>) where the value is controlled by React state. The UI reflects the state, and every change updates the state explicitly.

This ensures one source of truth for form values.

Example – Controlled Input

import React, { useState } from "react";

function ControlledForm() {
  // State holds the input value
  const [name, setName] = useState("");

  // Update state on input change
  const handleChange = (e) => setName(e.target.value);

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Submitted name: ${name}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* Controlled input: value comes from state */}
      <input type="text" value={name} onChange={handleChange} />
      <button type="submit">Submit</button>
    </form>
  );
}

export default ControlledForm;

📌 Here, value={name} makes the input controlled by state.


---

🔹 2. Uncontrolled Components

Definition:
An uncontrolled component lets the DOM itself handle form data. Instead of storing the value in React state, we use a ref to directly access the DOM value when needed.

This is useful for quick forms or integrating with non-React code.

Example – Uncontrolled Input

import React, { useRef } from "react";

function UncontrolledForm() {
  // Ref points to the DOM element
  const inputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    // Directly read value from DOM
    alert(`Submitted name: ${inputRef.current.value}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* Uncontrolled input: no state */}
      <input type="text" ref={inputRef} />
      <button type="submit">Submit</button>
    </form>
  );
}

export default UncontrolledForm;

📌 This avoids state updates, but reduces React’s control.


---

🔹 3. Container vs Presentational Components

Definition:
This pattern separates logic from UI.

Presentational components → Focus only on how things look (UI).

Container components → Handle how things work (state, data fetching, logic).


Example – Separation of Concerns

// Presentational (UI only)
function UserList({ users }) {
  return (
    <ul>
      {users.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

// Container (fetching + state)
function UserContainer() {
  const [users, setUsers] = React.useState([]);

  React.useEffect(() => {
    // Fake API call
    setTimeout(() => {
      setUsers([{ id: 1, name: "Tushar" }, { id: 2, name: "Sneha" }]);
    }, 1000);
  }, []);

  return <UserList users={users} />;
}

export default UserContainer;

📌 Benefits: cleaner, reusable UI and easier testing.


---

🔹 4. Higher-Order Components (HOCs)

Definition:
A Higher-Order Component (HOC) is a function that takes a component and returns a new component, adding extra logic or behavior.

It’s commonly used for cross-cutting concerns like logging, authentication, theming, etc.

Example – WithLogger HOC

import React from "react";

// HOC adds logging behavior
function withLogger(WrappedComponent) {
  return function EnhancedComponent(props) {
    console.log("Props received:", props);
    return <WrappedComponent {...props} />;
  };
}

// Base component
function Greeting({ name }) {
  return <h2>Hello, {name}</h2>;
}

// Enhanced component
const GreetingWithLogger = withLogger(Greeting);

export default function App() {
  return <GreetingWithLogger name="Tushar" />;
}

📌 Here, withLogger wraps Greeting to add logging without modifying the original component.


---

🔹 5. Render Props

Definition:
The Render Props pattern means passing a function as a prop that returns React elements. This lets us share logic between components while keeping UI flexible.

Example – Mouse Tracker

import React, { useState } from "react";

// Logic provider with render prop
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  return (
    <div
      style={{ height: "200px", border: "1px solid black" }}
      onMouseMove={(e) => setPosition({ x: e.clientX, y: e.clientY })}
    >
      {render(position)} {/* UI decided by parent */}
    </div>
  );
}

// Usage
function App() {
  return (
    <MouseTracker
      render={({ x, y }) => <p>Mouse position: {x}, {y}</p>}
    />
  );
}

export default App;

📌 The parent decides what to render, while MouseTracker just provides data.


---

🔹 6. Compound Components

Definition:
Compound components allow you to create a parent component with child components that work together, providing a flexible API for building complex UIs (like tabs, modals, dropdowns).

Example – Tabs System

import React, { useState } from "react";

// Parent component
function Tabs({ children }) {
  const [active, setActive] = useState(0);

  // Clone children and inject props
  return (
    <div>
      {React.Children.map(children, (child, index) =>
        React.cloneElement(child, {
          isActive: index === active,
          onClick: () => setActive(index),
        })
      )}
    </div>
  );
}

// Child Tab component
function Tab({ label, isActive, onClick }) {
  return (
    <button
      style={{
        fontWeight: isActive ? "bold" : "normal",
        marginRight: "10px",
      }}
      onClick={onClick}
    >
      {label}
    </button>
  );
}

// Example usage
function App() {
  return (
    <Tabs>
      <Tab label="Home" />
      <Tab label="Profile" />
      <Tab label="Settings" />
    </Tabs>
  );
}

export default App;

📌 This gives users a flexible API where they can simply write <Tabs><Tab ... /></Tabs> without worrying about internal logic.


---

🔑 Key Takeaways

Controlled Components → React manages form state.

Uncontrolled Components → DOM manages form state (via refs).

Container vs Presentational → Separate logic from UI.

HOCs → Wrap components with extra behavior.

Render Props → Share logic via functions as children.

Compound Components → Build flexible, reusable APIs.




Comments

Popular posts from this blog

🌟 Dot net Microservices interview questions

Here is a comprehensive list of 200 .NET microservices coding questions covering all core microservices concepts and cross-cutting concerns relevant for designing, building, deploying, and maintaining .NET-based distributed systems. 🧩 A. Microservices Fundamentals (20) Build a microservice in .NET 8 that exposes a simple CRUD API. Implement communication between two microservices using REST. How would you design microservices for an e-commerce application? Create a microservice that handles user registration and login. How do you isolate domain logic in a microservice? How to apply the "Single Responsibility Principle" in microservices? Design a service registry/discovery mechanism using custom middleware. Implement a service that handles file uploads and metadata separately. Build a stateless microservice and explain its benefits. Implement health check endpoints in .NET 8. Demonstrate versioning in a microservice API. Add Swagger/OpenAPI support to your m...

⚡ Part 1: Introduction to Generics in C#

🌍 Why Do We Need Generics? Imagine you want to create a stack (like a pile of books 📚): You can push items on top You can pop items off the top If we write a stack for integers : public class IntStack { private int[] items = new int[10]; private int index = 0; public void Push(int item) => items[index++] = item; public int Pop() => items[--index]; } 👉 Problem: This only works for int . What if we want a string stack ? Or a Customer stack ? We’d have to duplicate code for every type. 😢 ✅ Solution: Generics Generics let us create type-safe reusable code without duplication. We can say: “I don’t care what type it is yet — I’ll decide later.” 1) Generic Classes Here’s a generic stack : // Generic class "Stack<T>" // The <T> is a placeholder for any type public class Stack<T> { private T[] items = new T[10]; // Array of type T private int index = 0; // Push adds an item of type T public void P...

🚪 Part 9: API Gateway for .NET 8 Microservices (Ocelot & YARP)

Once you have multiple microservices (Products, Orders, Payments…), exposing each one directly to clients gets messy: Different base URLs Duplicated auth logic No unified rate limiting / caching Hard to evolve routes or aggregate data 👉 Enter the API Gateway — your single front door for all microservices. An API Gateway handles: ✅ Routing & path rewriting ✅ Load balancing, retries, circuit breakers ✅ Authentication & Authorization (JWT, OAuth2) ✅ Rate limiting & caching ✅ Aggregation (compose results from multiple services) In this post we’ll implement two strong options: Ocelot → config-driven, mature, DevOps-friendly YARP (Yet Another Reverse Proxy) → Microsoft’s code-first, extensible gateway ⚖️ Ocelot vs YARP — When to Choose Ocelot → JSON config, minimal C#, built-in QoS (rate limit, circuit breaker). Perfect for teams that like DevOps config-as-code. YARP → full C# control, middleware-friendly, can embed into broader apps (e.g. add dashb...