Components & Composition

Component is a reusable JavaScript Function or class that accepts Props and returns React JSX elements describing what should appear on the screen.

Functional Components

They are just standard JavaScript functions describing UI.

// 1. Define the component
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
 
// OR using Arrow Functions (Preferred by many)
const Welcome = (props) => {
  return <h1>Hello, {props.name}</h1>;
};
 
// 2. Use it
// <Welcome name="Sara" />

Key Characteristics:

  • Less Boilerplate: No classes, no render() method.
  • Hooks Support: They use useState and useEffect to handle data.

Class Components

Earlier, whenever we needed state, we had to use a Class Component.

  • Inheritance: They inherit from React.Component and must have a render() method.
  • UI Logic: Inside render(), we return JSX describing the UI.
  • Super: super() is called inside the constructor to initialize the parent class.
  • Data: Both state and props are attached to the class instance, so we access them via this.state and this.props.
  • Methods: Arrow Functions live directly on the instance and are accessed via this.
class Welcome extends React.Component {
  render() {
    // We access props via 'this.props'
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Key Characteristics:

  • Verbosity: Requires extending a class and a render method.
  • this Confusion: We often have to bind methods manually (e.g., this.handleClick.bind(this)).
  • Lifecycle Methods: Uses componentDidMount, componentWillUnmount.

Functional vs. Class Components: The Breakdown

FeatureFunctional ComponentsClass Components
SyntaxPlain JS FunctionES6 Class
State ManagementuseState Hookthis.state
Side EffectsuseEffect HookLifecycle Methods (componentDidMount)
BoilerplateLow (Minimal code)High (Classes, render, this)
PerformanceSlightly faster (less overhead)Slightly heavier
Best PracticeYes (Default for new apps)No (Legacy/Maintenance only)

Component Composition

In React, we don't use inheritance (like class AdminUser extends User). We use Composition.

Composition means building complex components from simpler ones.

The children Prop

This is a special prop that allows us to pass elements inside our component's opening and closing tags.

The Container (Wrapper):

const Card = (props) => {
  return (
    <div className="card-box">
      {/* This renders whatever is put inside <Card>...</Card> */}
      {props.children}
    </div>
  );
};

The Usage:

const App = () => {
  return (
    <Card>
      {/* All of this is passed as 'props.children' to Card */}
      <h2>User Profile</h2>
      <p>Name: John Doe</p>
    </Card>
  );
};

This pattern is critical for creating reusable layouts (like Modals, Cards, or Layout Wrappers) where the container doesn't care what is inside it.


Practical Example: Counter Component

Here is a full example of a React Class Component written in TypeScript. It uses interface for typing Props and State.

The Standard Implementation

This version uses a Constructor to initialize state, which was the standard pattern for many years.

"use client";
 
import { Button } from "@/components/ui/button";
import { Component, ReactNode } from "react";
 
// 1. Define Types for Props and State
interface CounterProps {
  initialCount?: number;
}
 
interface CounterState {
  count: number;
}
 
class CounterComponent extends Component<CounterProps, CounterState> {
  // 2. Constructor: Used to initialize state and bind methods (if not using arrows)
  constructor(props: CounterProps) {
    super(props); // Must call super to access 'this'
 
    this.state = {
      count: 0,
    };
  }
 
  // Runs ONCE after the component renders for the first time.
  // We use this for API calls, timers, or subscriptions.
  componentDidMount() {
    console.log("Mounted! (Equivalent to useEffect with [])");
  }
 
  // Runs whenever State or Props change.
  // We often check prevProps vs this.props here to avoid infinite loops.
  componentDidUpdate(prevProps: CounterProps, prevState: CounterState) {
    if (prevState.count !== this.state.count) {
      console.log(`Count changed: ${prevState.count} -> ${this.state.count}`);
      console.log("Updated! (Equivalent to useEffect with [count])");
    }
  }
 
  // Runs just before the component is removed from the DOM.
  componentWillUnmount() {
    console.log("Cleanup! (Equivalent to useEffect return function)");
  }
 
  // 3. Methods: Defined as Arrow Functions to auto-bind 'this'
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };
 
  decrement = () => {
    this.setState({ count: this.state.count - 1 });
  };
 
  render(): ReactNode {
    return (
      <div className="p-4 border rounded flex flex-col gap-1 justify-center">
        <h1 className="text-4xl text-center">{this.state.count}</h1>
        <div className="flex gap-1">
          <Button
            onClick={this.increment}
            variant="outline"
            className="rounded bg-muted/5 hover:bg-muted/30 transition-all duration-300"
          >
            +
          </Button>
          <Button
            onClick={this.decrement}
            variant="outline"
            className="rounded bg-muted/5 hover:bg-muted/30 transition-all duration-300"
          >
            -
          </Button>
        </div>
      </div>
    );
  }
}
 
export default CounterComponent;

📝 Summary Table

ConceptDefinitionKey Takeaway
ComponentA reusable piece of UI.Splits UI into independent, isolated pieces.
Functional ComponentA function returning JSX.The modern standard. Uses Hooks.
Class ComponentA class with a render() method.Legacy. Uses this and lifecycle methods.
CompositionNesting components inside others.Powerful pattern to reuse UI logic/layout.
props.childrenSpecial prop for nested content.Allows components to act as wrappers.

🛑 Stop and Think

1. In a Class component, if we try to access props.name without this, what will happen?

We will get a ReferenceError: props is not defined.

  • Why: In a Class, props is not a variable passed into the render() function as an argument. It is a property stored on the instance of the class itself.
  • Therefore, we must strictly access it via this.props.
  • Note: This is one of the main reasons people disliked classes forgetting this caused constant bugs. Functional components fix this because props is just a standard function argument.

2. Can a component return undefined or null? If so, what does React render?

Answer:

  • null: Yes! This is the standard way to tell React to render nothing (hide the component). It is valid and common.
  • undefined: In older React versions, returning undefined was considered an error (often meaning dev forgot the return statement). In modern React, it acts like null (renders nothing), but relies on implicit behavior. It is best practice to explicitly return null if we want to render nothing.