Skip to main content

Documentation Index

Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt

Use this file to discover all available pages before exploring further.

Components & Props

Components are the building blocks of any React application. They let you split the UI into independent, reusable pieces, and think about each piece in isolation. The LEGO analogy: A LEGO set gives you individual bricks (components) that each serve a purpose — a window block, a roof tile, a door. You can combine them to build a house, rearrange them to build a spaceship, or reuse the same window block in both. React components work the same way: a Button, a Header, a Card — each is self-contained, reusable, and composable. The difference is that React components can accept data (props) that change how they look and behave, like a LEGO brick that changes color based on what you tell it.

Component Mental Model

Think of components like LEGO blocks:
┌─────────────────────────────────────────────────────────────────┐
│                         App Component                           │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    Header Component                      │   │
│  │  ┌──────────────┐  ┌──────────────────────────────────┐ │   │
│  │  │    Logo      │  │         Navigation               │ │   │
│  │  └──────────────┘  └──────────────────────────────────┘ │   │
│  └─────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                     Main Content                         │   │
│  │  ┌─────────────────┐  ┌─────────────────────────────┐   │   │
│  │  │   ProductCard   │  │        ProductCard          │   │   │
│  │  └─────────────────┘  └─────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    Footer Component                      │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Functional Components

In modern React, we primarily use Functional Components. They are simply JavaScript functions that return JSX.
// Function Declaration
function Welcome() {
  return <h1>Hello, World</h1>;
}

// Arrow Function (more common)
const Welcome = () => {
  return <h1>Hello, World</h1>;
};

// Arrow Function with implicit return
const Welcome = () => <h1>Hello, World</h1>;

Component Naming Rules

Component names must start with a capital letter. React treats lowercase elements as HTML tags.
// ❌ Wrong - React treats this as an HTML tag
const welcome = () => <h1>Hi</h1>;
<welcome /> // Renders as <welcome></welcome>

// ✅ Correct
const Welcome = () => <h1>Hi</h1>;
<Welcome /> // Renders the component

Importing and Exporting Components

// Welcome.jsx - Named export
export const Welcome = () => <h1>Hello</h1>;

// Welcome.jsx - Default export
const Welcome = () => <h1>Hello</h1>;
export default Welcome;

// App.jsx - Importing
import Welcome from './Welcome';           // Default import
import { Welcome } from './Welcome';       // Named import
import { Welcome as Greeting } from './Welcome'; // Renamed import

Props (Properties)

Props are the way to pass data from parent to child components. Think of them as function arguments — because that is literally what they are. A React component is a function, and props are its parameters. Just like you would call greet("Alice") to pass a name to a function, you write <Welcome name="Alice" /> to pass a name to a component.

Passing Props

function App() {
  return (
    <div>
      <Welcome name="Sara" age={25} isOnline={true} />
      <Welcome name="Bob" age={30} isOnline={false} />
    </div>
  );
}

Receiving Props

The component receives all props as a single object.
// Method 1: Props object
function Welcome(props) {
  return (
    <h1>
      Hello, {props.name}. You are {props.age} years old.
      {props.isOnline && <span> 🟢</span>}
    </h1>
  );
}

// Method 2: Destructuring (recommended)
function Welcome({ name, age, isOnline }) {
  return (
    <h1>
      Hello, {name}. You are {age} years old.
      {isOnline && <span> 🟢</span>}
    </h1>
  );
}

Props Are Read-Only (Immutable)

Never modify props! Props are read-only. Attempting to change them violates React’s core principles.
// ❌ NEVER do this
function BadComponent(props) {
  props.name = 'Changed'; // ERROR!
  return <h1>{props.name}</h1>;
}

// ✅ If you need to modify data, use state (covered in next chapter)

Default Props

You can provide default values for props when they’re not passed.
// Method 1: Default parameters (recommended)
function Button({ color = 'blue', size = 'medium', text }) {
  return (
    <button 
      style={{ backgroundColor: color }}
      className={`btn-${size}`}
    >
      {text}
    </button>
  );
}

// Method 2: defaultProps (older approach)
function Button({ color, size, text }) {
  // ...
}
Button.defaultProps = {
  color: 'blue',
  size: 'medium'
};

Children Prop

children is a special prop that allows you to pass components or content between the opening and closing tags.
function Card({ children, title }) {
  return (
    <div className="card">
      <h2 className="card-title">{title}</h2>
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

function App() {
  return (
    <Card title="Welcome">
      <p>This is the card content.</p>
      <button>Click me</button>
    </Card>
  );
}

Practical Children Patterns

// Layout component
function PageLayout({ children }) {
  return (
    <div className="page">
      <Header />
      <main>{children}</main>
      <Footer />
    </div>
  );
}

// Modal component
function Modal({ children, isOpen, onClose }) {
  if (!isOpen) return null;
  
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>
  );
}

// Usage
<Modal isOpen={showModal} onClose={() => setShowModal(false)}>
  <h2>Confirm Action</h2>
  <p>Are you sure you want to proceed?</p>
  <button onClick={handleConfirm}>Yes</button>
</Modal>

Prop Types (Runtime Validation)

PropTypes provide runtime type checking for your components.
npm install prop-types
import PropTypes from 'prop-types';

function UserCard({ name, age, email, isAdmin, avatar }) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Email: {email}</p>
      {isAdmin && <span className="badge">Admin</span>}
    </div>
  );
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
  email: PropTypes.string.isRequired,
  isAdmin: PropTypes.bool,
  avatar: PropTypes.string
};

UserCard.defaultProps = {
  isAdmin: false,
  avatar: '/default-avatar.png'
};

Common PropTypes

PropTypeDescription
PropTypes.stringString value
PropTypes.numberNumber value
PropTypes.boolBoolean value
PropTypes.arrayArray
PropTypes.objectObject
PropTypes.funcFunction
PropTypes.nodeAnything renderable (string, number, element)
PropTypes.elementReact element
PropTypes.oneOf(['a', 'b'])One of specific values
PropTypes.arrayOf(PropTypes.number)Array of specific type
PropTypes.shape({ name: PropTypes.string })Object with specific shape
For production apps, consider using TypeScript instead of PropTypes. It provides compile-time checking and better IDE support.

Component Composition Patterns

1. Container and Presentational Components

Separate logic from presentation:
// Presentational Component (dumb) - only handles display
function UserList({ users, onSelect }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id} onClick={() => onSelect(user)}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}

// Container Component (smart) - handles logic
function UserListContainer() {
  const [users, setUsers] = useState([]);
  const [selectedUser, setSelectedUser] = useState(null);

  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(setUsers);
  }, []);

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

2. Compound Components

Components that work together to form a complete UI:
// Accordion compound component
function Accordion({ children }) {
  const [openIndex, setOpenIndex] = useState(null);
  
  return (
    <div className="accordion">
      {React.Children.map(children, (child, index) =>
        React.cloneElement(child, {
          isOpen: index === openIndex,
          onToggle: () => setOpenIndex(index === openIndex ? null : index)
        })
      )}
    </div>
  );
}

function AccordionItem({ title, children, isOpen, onToggle }) {
  return (
    <div className="accordion-item">
      <button onClick={onToggle} className="accordion-header">
        {title}
        <span>{isOpen ? '−' : '+'}</span>
      </button>
      {isOpen && <div className="accordion-content">{children}</div>}
    </div>
  );
}

// Usage
<Accordion>
  <AccordionItem title="Section 1">Content 1</AccordionItem>
  <AccordionItem title="Section 2">Content 2</AccordionItem>
  <AccordionItem title="Section 3">Content 3</AccordionItem>
</Accordion>

3. Render Props

Pass a function as a child to share logic:
function MouseTracker({ children }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', handleMove);
    return () => window.removeEventListener('mousemove', handleMove);
  }, []);

  return children(position);
}

// Usage
<MouseTracker>
  {({ x, y }) => (
    <div>Mouse position: {x}, {y}</div>
  )}
</MouseTracker>

Spreading Props

You can spread all props to a child component:
function Button({ className, children, ...rest }) {
  return (
    <button className={`btn ${className}`} {...rest}>
      {children}
    </button>
  );
}

// All native button props (onClick, disabled, type, etc.) are passed through
<Button 
  className="primary" 
  onClick={handleClick} 
  disabled={isLoading}
  type="submit"
>
  Submit
</Button>

🎯 Practice Exercises

Build an Alert component that accepts:
  • type: ‘success’ | ‘warning’ | ‘error’
  • title: string
  • children: content
function Alert({ type = 'info', title, children }) {
  const styles = {
    success: { bg: '#d1fae5', border: '#10b981', color: '#065f46' },
    warning: { bg: '#fef3c7', border: '#f59e0b', color: '#92400e' },
    error: { bg: '#fee2e2', border: '#ef4444', color: '#991b1b' },
    info: { bg: '#dbeafe', border: '#3b82f6', color: '#1e40af' }
  };

  const style = styles[type];

  return (
    <div style={{
      backgroundColor: style.bg,
      borderLeft: `4px solid ${style.border}`,
      padding: '16px',
      marginBottom: '16px'
    }}>
      {title && <strong style={{ color: style.color }}>{title}</strong>}
      <p style={{ color: style.color, margin: title ? '8px 0 0' : 0 }}>
        {children}
      </p>
    </div>
  );
}

// Usage
<Alert type="success" title="Success!">
  Your changes have been saved.
</Alert>

<Alert type="error">
  Something went wrong. Please try again.
</Alert>
Create a flexible card system with Card, Card.Header, Card.Body, and Card.Footer:
function Card({ children, className = '' }) {
  return (
    <div className={`card ${className}`} style={{
      border: '1px solid #e5e7eb',
      borderRadius: '8px',
      overflow: 'hidden'
    }}>
      {children}
    </div>
  );
}

Card.Header = function CardHeader({ children }) {
  return (
    <div style={{ padding: '16px', borderBottom: '1px solid #e5e7eb' }}>
      {children}
    </div>
  );
};

Card.Body = function CardBody({ children }) {
  return <div style={{ padding: '16px' }}>{children}</div>;
};

Card.Footer = function CardFooter({ children }) {
  return (
    <div style={{ 
      padding: '16px', 
      borderTop: '1px solid #e5e7eb',
      backgroundColor: '#f9fafb'
    }}>
      {children}
    </div>
  );
};

// Usage
<Card>
  <Card.Header>
    <h3>Card Title</h3>
  </Card.Header>
  <Card.Body>
    <p>Card content goes here.</p>
  </Card.Body>
  <Card.Footer>
    <button>Action</button>
  </Card.Footer>
</Card>
Create an Avatar component that shows initials if no image is provided:
function Avatar({ src, name, size = 40 }) {
  const getInitials = (name) => {
    return name
      .split(' ')
      .map(word => word[0])
      .join('')
      .toUpperCase()
      .slice(0, 2);
  };

  const colors = ['#ef4444', '#f97316', '#eab308', '#22c55e', '#3b82f6', '#8b5cf6'];
  const colorIndex = name.charCodeAt(0) % colors.length;

  const style = {
    width: size,
    height: size,
    borderRadius: '50%',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: size * 0.4,
    fontWeight: 'bold',
    color: 'white',
    backgroundColor: colors[colorIndex]
  };

  if (src) {
    return (
      <img 
        src={src} 
        alt={name} 
        style={{ ...style, objectFit: 'cover' }}
        onError={(e) => e.target.style.display = 'none'}
      />
    );
  }

  return <div style={style}>{getInitials(name)}</div>;
}

// Usage
<Avatar src="/user.jpg" name="John Doe" size={50} />
<Avatar name="Jane Smith" size={40} />

Summary

ConceptDescription
Functional ComponentsFunctions that return JSX
PropsArguments passed to components (read-only)
DestructuringExtract props for cleaner code
Default PropsFallback values when props aren’t provided
childrenSpecial prop for nested content
PropTypesRuntime type checking
CompositionBuilding complex UIs from simple components
Spreading PropsPass all props with {...props}

Next Steps

In the next chapter, you’ll learn about State & useState — how to make your components interactive and dynamic!

Interview Deep-Dive

Strong Answer: The children prop passes static JSX content between a component’s opening and closing tags. The parent decides what to render, and the wrapper component just places it somewhere in its layout. It is the simplest composition pattern — think of it like a picture frame: the frame does not care what picture you put in it.Render props (including function-as-children) are fundamentally different because the parent component passes a function that receives data and returns JSX. This means the wrapper component can share its internal state or computed values with the consumer without exposing them through Context or lifting state up. The classic example is a MouseTracker that tracks cursor position internally and passes { x, y } to the render function so the consumer can decide how to display it.In practice, I reach for children when the wrapper is a layout concern (cards, modals, page shells) and does not need to pass data back. I use render props when the wrapper owns logic that the consumer needs access to — for instance, a Formik component that manages form state and passes { values, errors, handleChange } to a render function.That said, since hooks arrived, most render prop patterns have been replaced by custom hooks. Instead of a <MouseTracker> render prop, you write useMouse() and get { x, y } directly. Hooks are more composable and avoid the nesting pyramid that render props create. The only place render props still shine is when you need to conditionally render children based on the wrapper’s internal state and the wrapper also controls DOM structure (like a headless dropdown component).Follow-up: The compound components pattern uses React.Children.map and React.cloneElement. What are the downsides of this approach and what is the modern alternative?React.Children.map and cloneElement are fragile. They only work if the compound children are direct children — if someone wraps them in a div or a fragment, cloneElement injects props into the wrong element. They also break TypeScript’s type safety because the injected props are invisible to the type system. And they create an implicit API contract: the parent “magically” injects props that the child expects but never declares.The modern alternative is to use Context. The parent compound component (like Accordion) provides state through a Context, and each child (AccordionItem) consumes it with useContext. This works regardless of nesting depth, plays well with TypeScript, and makes the data flow explicit. Libraries like Radix UI and Headless UI use this pattern extensively.The tradeoff is slightly more boilerplate (creating a Context) and the performance implications of Context re-renders. But for compound components with a handful of children, this is negligible.
Strong Answer: I would start with concrete signals rather than abstract principles. You should split a component when you see any of these:First, the component does two unrelated things. If a component fetches user data AND renders a chart, those are two concerns. The data fetching should live in a container or custom hook, and the chart should be its own presentational component. The test for this: can you describe what the component does in one sentence without using “and”?Second, you see duplicated JSX patterns. If three pages each render a card with an avatar, name, and action button, extract a UserCard component. The rule of thumb is: if you copy-paste JSX more than once, extract it.Third, the component is hard to test. If you need to mock five APIs and set up complex state just to test whether a button turns blue, the component is doing too much. Smaller components have smaller test surfaces.Fourth, the file exceeds roughly 200-300 lines. This is not a hard rule, but if you are scrolling a lot, it is a smell.What I would not do is split prematurely. A component that renders a form with five fields does not need five separate FormField components on day one. Over-splitting creates indirection without benefit — you end up jumping between 15 files to understand one screen. I follow the “rule of three”: the first time you write something, just write it. The second time, note the duplication. The third time, extract it.The key mental model is: components are functions, and the same principles apply. A function should do one thing, be testable in isolation, and have a clear interface (props). If your component violates any of these, it is a candidate for splitting.Follow-up: How do you decide whether shared logic should be a component, a custom hook, or a utility function?If the shared logic involves JSX output, it is a component. If it involves React state or lifecycle (hooks), it is a custom hook. If it is pure data transformation with no React dependency, it is a utility function.Concrete example: formatting a date is a utility function — formatDate(timestamp). Tracking window dimensions with a resize listener is a custom hook — useWindowSize(). Rendering a formatted timestamp with a tooltip showing the relative time is a component — <Timestamp value={date} />.The mistake I see most often is putting pure logic into hooks unnecessarily. If your “hook” does not call useState, useEffect, or another hook, it should not be a hook at all — it should be a plain function. Making it a hook adds the Rules of Hooks constraints for no benefit.
Strong Answer: Props are read-only because React’s rendering model depends on unidirectional data flow: data flows down from parent to child, and the parent is the single source of truth for any prop value. If a child could mutate its props, the parent’s state and the child’s view of that state would diverge, leading to inconsistencies that are extremely hard to debug.If you literally do props.name = 'changed' in JavaScript strict mode (which React enforces in development), you get a TypeError because React freezes the props object with Object.freeze() in development mode. In production builds, Object.freeze is stripped for performance, so the mutation would silently succeed at the JavaScript level — but React would not know about it and would not re-render. The UI would be stale, or worse, you would corrupt shared object references if the parent passes the same object to multiple children.The deeper principle is that React components should be pure functions of their props and state. Given the same props, a component should produce the same output. Mutating props violates this contract and makes components unpredictable, untestable, and impossible to optimize with React.memo (which relies on shallow comparison of the previous and next props).In practice, if you need to “change” a prop, you have two options: lift the state up to the parent and pass a callback (onNameChange), or use local state initialized from the prop (useState(props.initialName)). The second approach is common for “initial value” props where the child needs to diverge from the parent’s value (like a form input with a default).Follow-up: What is the difference between passing initialValue as a prop versus value as a prop? How does this affect component design?This is the distinction between controlled and uncontrolled components. When you pass value, the parent owns the state and the component must call back via onChange to request changes — the parent is in full control. When you pass initialValue, the component copies it into local state on mount and manages it independently from that point.The tradeoff: controlled components give the parent full authority over the value (useful for validation, formatting, dependent fields) but require the parent to manage state for every input. Uncontrolled components are simpler for the parent but harder to synchronize if the parent later needs to reset or override the value.A common bug with initialValue is expecting the component to update when the prop changes after mount. Since useState(initialValue) only reads the initial value once, changing the prop later has no effect. If you need to reset, you either need a key change to remount the component or a useEffect that syncs the prop to state — which is an anti-pattern the React docs explicitly warn against. The cleanest solution is using key={userId} on the component so React unmounts and remounts it with fresh state when the user changes.