React Component Architecture

Module 22: Web Frameworks I (JavaScript) - Wednesday: React Fundamentals

Introduction to React

React is a JavaScript library for building user interfaces, particularly single-page applications where UI updates are frequent and performance is critical. Created by Facebook (now Meta) and released in 2013, React has become one of the most popular front-end libraries due to its efficiency, flexibility, and powerful component model.

graph TD A[React Application] --> B[Components] B --> C[JSX] B --> D[Props] B --> E[State] C --> F[Virtual DOM] D --> F E --> F F --> G[DOM Updates] style A fill:#f9d71c,stroke:#333,stroke-width:2px style B fill:#a1ffa8,stroke:#333,stroke-width:2px style F fill:#a1c2ff,stroke:#333,stroke-width:2px

The LEGO Analogy

Think of React components as LEGO blocks:

  • Modular and Reusable: Just as LEGO blocks can be used in multiple constructions, React components can be reused across your application
  • Composable: Small LEGO pieces combine to create larger structures; similarly, small React components combine to create complex UIs
  • Self-Contained: Each LEGO piece has a specific shape and function, just as React components encapsulate their own markup, logic, and styling
  • Instructions (Props): LEGO sets come with instructions on how to assemble pieces; similarly, React components receive "instructions" via props
  • Internal Mechanisms (State): Some advanced LEGO pieces have internal moving parts; likewise, React components can have internal state that affects their behavior

Just as you can build anything from simple toys to complex architectural models with LEGO, you can build anything from simple widgets to complex web applications with React components.

Core React Philosophies

Why Companies Choose React

React has been adopted by thousands of companies, from startups to enterprises:

  • Facebook/Meta: Created React to solve their scaling UI challenges with their news feed
  • Airbnb: Uses React to build their user interface and component library, enabling their designers and developers to work more efficiently
  • Netflix: Rebuilt their platform using React to improve performance and development velocity
  • Instagram: Uses React in their web application to provide a seamless user experience
  • Discord: Relies on React for their web client to provide real-time updates and smooth interactions

Companies cite several benefits when adopting React:

  • Improved developer productivity through reusable components
  • Better performance for dynamic UIs with frequent updates
  • Robust ecosystem with extensive libraries and tools
  • Large talent pool of developers familiar with React
  • Strong community support and continuous improvements

Understanding Components

Components are the building blocks of React applications. They are self-contained, reusable pieces of code that return markup (via JSX).

Component Types

React components generally fall into two main categories:

graph TD A[React Components] --> B[Functional Components] A --> C[Class Components] B --> D["Simple, stateless
(before Hooks)"] B --> E["Full-featured
(with Hooks)"] C --> F[Lifecycle Methods] C --> G[this.state] C --> H[this.props] style A fill:#a1c2ff,stroke:#333,stroke-width:2px style B fill:#a1ffa8,stroke:#333,stroke-width:2px style C fill:#ffd1a1,stroke:#333,stroke-width:2px

Functional Component Example


// A simple functional component
function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}

// Using arrow function syntax 
const Greeting = (props) => {
  return <h1>Hello, {props.name}!</h1>;
};

// Even more concise with implicit return
const Greeting = props => <h1>Hello, {props.name}!</h1>;

// Using destructuring for cleaner code
const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;
                

This example shows various ways to write functional components, from the traditional function declaration to more concise arrow functions with destructuring.

Class Component Example


// A class component example
import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  
  incrementCount = () => {
    this.setState({ count: this.state.count + 1 });
  }
  
  render() {
    return (
      <div>
        <h2>Count: {this.state.count}</h2>
        <button onClick={this.incrementCount}>Increment</button>
      </div>
    );
  }
}

export default Counter;
                

This class component demonstrates how to handle state and events in the traditional class-based approach. While class components are still supported, functional components with Hooks are now the recommended approach.

Modern Functional Component with Hooks


// The same Counter component as a functional component with Hooks
import React, { useState } from 'react';

function Counter() {
  // useState returns a stateful value and a function to update it
  const [count, setCount] = useState(0);
  
  const incrementCount = () => {
    setCount(count + 1);
  };
  
  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={incrementCount}>Increment</button>
    </div>
  );
}

export default Counter;
                

This modern approach uses the useState Hook to add state to a functional component. With Hooks (introduced in React 16.8), functional components can now handle state, lifecycle events, context, and more.

Component Design Philosophies

When designing components, developers often categorize them by their purpose and scope:

Presentational vs. Container Components

  • Presentational Components: Focus on how things look (markup, styles)
    • Primarily concerned with the UI
    • Receive data and callbacks through props
    • Rarely have their own state (except for UI state)
    • Example: Button, Card, Modal
  • Container Components: Focus on how things work (data fetching, state)
    • Concerned with data operations and behavior
    • Provide data and behavior to presentational components
    • Often stateful and connected to data sources
    • Example: UserList, ProductDetail, ShoppingCart

Atomic Design Methodology

This design system methodology breaks down components into five distinct levels:

  1. Atoms: Basic building blocks (buttons, inputs, labels)
  2. Molecules: Simple groups of UI elements functioning together (search form, menu item)
  3. Organisms: Complex UI components composed of molecules (navigation header, product grid)
  4. Templates: Page layouts without content (article layout, profile page)
  5. Pages: Specific instances of templates with real content

Companies like Airbnb, IBM (with Carbon), and Material-UI use similar component hierarchies to maintain consistency and reusability in their design systems.

Component Composition Patterns

Component composition is a powerful pattern in React that enables building complex UIs from simpler components. Here are key patterns for effective composition:

Basic Composition


// Basic component composition
function App() {
  return (
    <div className="app">
      <Header />
      <MainContent />
      <Footer />
    </div>
  );
}

function Header() {
  return (
    <header>
      <Logo />
      <Navigation />
    </header>
  );
}

function MainContent() {
  return (
    <main>
      <Sidebar />
      <ContentArea />
    </main>
  );
}

function Footer() {
  return (
    <footer>
      <Copyright />
      <SocialLinks />
    </footer>
  );
}
                

This example demonstrates how to build a page layout by composing smaller components. Each component is responsible for a specific part of the UI, making the code more maintainable and easier to understand.

Children Props


// Using the children prop for flexible composition
function Card({ title, children }) {
  return (
    <div className="card">
      <div className="card-header">
        <h2>{title}</h2>
      </div>
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

// Using the Card component
function ProfileCard() {
  return (
    <Card title="User Profile">
      <img src="avatar.jpg" alt="User Avatar" />
      <h3>John Doe</h3>
      <p>Frontend Developer</p>
      <button>Edit Profile</button>
    </Card>
  );
}

function NotificationCard() {
  return (
    <Card title="Notifications">
      <ul>
        <li>New message from Jane</li>
        <li>Meeting at 3 PM</li>
        <li>Task deadline approaching</li>
      </ul>
    </Card>
  );
}
                

The children prop allows a component to accept and render arbitrary content. This pattern creates highly reusable container components like Card, Modal, or Layout components.

Specialized Components


// Creating specialized components
function Button({ variant = 'primary', size = 'medium', children, onClick }) {
  const className = `btn btn-${variant} btn-${size}`;
  
  return (
    <button className={className} onClick={onClick}>
      {children}
    </button>
  );
}

// Creating specialized versions
function PrimaryButton(props) {
  return <Button variant="primary" {...props} />;
}

function SecondaryButton(props) {
  return <Button variant="secondary" {...props} />;
}

function DangerButton(props) {
  return <Button variant="danger" {...props} />;
}

// Usage
function ActionPanel() {
  return (
    <div>
      <PrimaryButton onClick={() => console.log('Save')}>Save</PrimaryButton>
      <SecondaryButton onClick={() => console.log('Cancel')}>Cancel</SecondaryButton>
      <DangerButton onClick={() => console.log('Delete')}>Delete</DangerButton>
    </div>
  );
}
                

This pattern creates specialized components that are configured with specific props, simplifying usage and ensuring consistency. The specialized components are essentially pre-configured versions of a more general component.

Render Props Pattern


// The render props pattern
function DataFetcher({ url, render }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    setLoading(true);
    
    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
  }, [url]);
  
  return render({ data, loading, error });
}

// Usage
function UserProfile({ userId }) {
  return (
    <DataFetcher 
      url={`https://api.example.com/users/${userId}`} 
      render={({ data, loading, error }) => {
        if (loading) return <div>Loading...</div>;
        if (error) return <div>Error: {error.message}</div>;
        if (!data) return <div>No data</div>;
        
        return (
          <div>
            <h2>{data.name}</h2>
            <p>Email: {data.email}</p>
            <p>Role: {data.role}</p>
          </div>
        );
      }}
    />
  );
}
                

The render props pattern provides a powerful way to share functionality between components. It involves passing a function as a prop that tells the component what to render. This pattern is useful for abstract components that handle logic but delegate rendering.

Compound Components Pattern


// Compound components pattern
import React, { createContext, useContext, useState } from 'react';

// Create a context for the tabs
const TabsContext = createContext();

// Main container component
function Tabs({ children, defaultIndex = 0 }) {
  const [activeIndex, setActiveIndex] = useState(defaultIndex);
  
  return (
    <TabsContext.Provider value={{ activeIndex, setActiveIndex }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

// Tab list component
Tabs.List = function TabList({ children }) {
  return <div className="tabs-list">{children}</div>;
};

// Individual tab component
Tabs.Tab = function Tab({ children, index }) {
  const { activeIndex, setActiveIndex } = useContext(TabsContext);
  const isActive = activeIndex === index;
  
  return (
    <button 
      className={`tab ${isActive ? 'active' : ''}`}
      onClick={() => setActiveIndex(index)}
    >
      {children}
    </button>
  );
};

// Tab panel component
Tabs.Panel = function TabPanel({ children, index }) {
  const { activeIndex } = useContext(TabsContext);
  
  if (activeIndex !== index) return null;
  
  return <div className="tab-panel">{children}</div>;
};

// Usage
function TabExample() {
  return (
    <Tabs>
      <Tabs.List>
        <Tabs.Tab index={0}>Profile</Tabs.Tab>
        <Tabs.Tab index={1}>Settings</Tabs.Tab>
        <Tabs.Tab index={2}>Notifications</Tabs.Tab>
      </Tabs.List>
      
      <Tabs.Panel index={0}>
        <h2>Profile Content</h2>
        <p>User profile information goes here.</p>
      </Tabs.Panel>
      
      <Tabs.Panel index={1}>
        <h2>Settings Content</h2>
        <p>User settings form goes here.</p>
      </Tabs.Panel>
      
      <Tabs.Panel index={2}>
        <h2>Notifications Content</h2>
        <p>User notifications list goes here.</p>
      </Tabs.Panel>
    </Tabs>
  );
}
                

Compound components provide an expressive, declarative API where components work together to create a cohesive experience. They share state implicitly through context rather than using explicit props, resulting in a more intuitive API.

Real-World Component Organization

In production applications, components are typically organized into structured directories that reflect their purpose and relationships:


src/
├── components/
│   ├── common/             # Reusable across the app
│   │   ├── Button/
│   │   │   ├── Button.jsx
│   │   │   ├── Button.test.jsx
│   │   │   ├── Button.module.css
│   │   │   └── index.js
│   │   ├── Card/
│   │   ├── Modal/
│   │   └── ...
│   ├── layout/             # Page structure components
│   │   ├── Header/
│   │   ├── Footer/
│   │   ├── Sidebar/
│   │   └── ...
│   ├── forms/              # Form-related components
│   │   ├── TextField/
│   │   ├── SelectField/
│   │   ├── Checkbox/
│   │   └── ...
│   └── features/           # Feature-specific components
│       ├── UserProfile/
│       ├── Dashboard/
│       ├── Authentication/
│       └── ...
├── hooks/                  # Custom React hooks
├── contexts/               # React context providers
├── utils/                  # Helper functions
├── pages/                  # Page components
└── ...
                

This organization strategy has several benefits:

  • Discoverability: Easy to find and use existing components
  • Scalability: Structure works for both small and large applications
  • Reusability: Clear separation between common and feature-specific components
  • Maintainability: Related files are grouped together
  • Testability: Components are isolated and easier to test

Companies like Airbnb, Shopify, and Microsoft implement similar patterns in their React codebases, often with design systems that document and standardize component usage across teams.

Props and Component Communication

Props (short for "properties") are React's mechanism for passing data from parent to child components. They are the primary way components communicate with each other.

graph TD A[Parent Component] -->|props| B[Child Component 1] A -->|props| C[Child Component 2] C -->|props| D[Grandchild Component] style A fill:#a1c2ff,stroke:#333,stroke-width:2px

Basic Props Usage


// Parent component passing props
function ParentComponent() {
  const userName = "Alice";
  const userRole = "Admin";
  
  return (
    <div>
      <h1>User Dashboard</h1>
      <UserProfile 
        name={userName} 
        role={userRole} 
        isActive={true} 
        loginCount={42}
      />
    </div>
  );
}

// Child component receiving props
function UserProfile(props) {
  return (
    <div className="user-profile">
      <h2>{props.name}</h2>
      <p>Role: {props.role}</p>
      <p>Status: {props.isActive ? 'Active' : 'Inactive'}</p>
      <p>Login count: {props.loginCount}</p>
    </div>
  );
}

// Destructuring props for cleaner code
function UserProfile({ name, role, isActive, loginCount }) {
  return (
    <div className="user-profile">
      <h2>{name}</h2>
      <p>Role: {role}</p>
      <p>Status: {isActive ? 'Active' : 'Inactive'}</p>
      <p>Login count: {loginCount}</p>
    </div>
  );
}
                

This example demonstrates how props are passed from a parent component to a child component, and how they can be accessed in the child component.

Default Props


// Setting default props
function Button({ type = 'button', variant = 'primary', children, onClick }) {
  const className = `btn btn-${variant}`;
  
  return (
    <button type={type} className={className} onClick={onClick}>
      {children}
    </button>
  );
}

// Using the component with default props
function ActionPanel() {
  return (
    <div>
      {/* Uses default variant 'primary' */}
      <Button onClick={() => console.log('Save clicked')}>Save</Button>
      
      {/* Overrides default variant */}
      <Button variant="secondary" onClick={() => console.log('Cancel clicked')}>
        Cancel
      </Button>
      
      {/* Overrides both defaults */}
      <Button type="submit" variant="success">Submit Form</Button>
    </div>
  );
}

// Alternative approach for class components
class Button extends React.Component {
  static defaultProps = {
    type: 'button',
    variant: 'primary'
  };
  
  render() {
    const { type, variant, children, onClick } = this.props;
    const className = `btn btn-${variant}`;
    
    return (
      <button type={type} className={className} onClick={onClick}>
        {children}
      </button>
    );
  }
}
                

Default props provide fallback values for props that aren't specified by the parent component. This makes components more flexible and easier to use.

Props Validation with PropTypes


// First, install the package: npm install prop-types
import PropTypes from 'prop-types';

function UserProfile({ name, age, email, isActive, roles }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Email: {email}</p>
      <p>Status: {isActive ? 'Active' : 'Inactive'}</p>
      <div>
        <h3>Roles:</h3>
        <ul>
          {roles.map(role => (
            <li key={role.id}>{role.name}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

// Define the expected prop types
UserProfile.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  email: PropTypes.string.isRequired,
  isActive: PropTypes.bool,
  roles: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.number.isRequired,
      name: PropTypes.string.isRequired
    })
  ).isRequired
};

// Define default props
UserProfile.defaultProps = {
  age: 0,
  isActive: false
};
                

PropTypes provide runtime type checking for React props. They help catch bugs by validating the data passed to components and serve as documentation for how components should be used.

Event Handling with Props


// Parent component with event handlers
function UserForm() {
  const handleSubmit = (userData) => {
    console.log('Form submitted with:', userData);
    // Save data to server, update state, etc.
  };
  
  const handleCancel = () => {
    console.log('Form canceled');
    // Reset form, navigate away, etc.
  };
  
  return (
    <div>
      <h2>User Form</h2>
      <UserFormContent 
        onSubmit={handleSubmit} 
        onCancel={handleCancel} 
      />
    </div>
  );
}

// Child component receiving event handler props
function UserFormContent({ onSubmit, onCancel }) {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  
  const handleLocalSubmit = (e) => {
    e.preventDefault();
    // Call the passed-in handler with the form data
    onSubmit({ name, email });
  };
  
  return (
    <form onSubmit={handleLocalSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          id="name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </div>
      
      <div>
        <label htmlFor="email">Email:</label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </div>
      
      <div>
        <button type="submit">Submit</button>
        <button type="button" onClick={onCancel}>Cancel</button>
      </div>
    </form>
  );
}
                

Passing event handlers as props is a common pattern for enabling child-to-parent communication. The child component calls the function passed by the parent, often with data that the parent needs.

Props Best Practices

In professional React applications, developers follow several best practices for prop usage:

Keep Components Focused

Avoid "prop drilling" (passing props through multiple levels of components) by:

  • Using React Context for deeply shared data
  • Decomposing components to minimize props needed
  • Considering state management libraries for complex apps

Prop Naming Conventions

  • Use clear, descriptive names (e.g., isDisabled instead of disabled)
  • Use camelCase for prop names
  • Prefix boolean props with "is", "has", or "should" (e.g., isVisible, hasError)
  • Prefix event handlers with "on" (e.g., onClick, onSubmit)
  • Prefix event handler implementations with "handle" (e.g., handleClick, handleSubmit)

Props Spreading

The spread operator can be used to pass all props from a parent to a child:


// Using spread operator for props
function Button(props) {
  return <button {...props} className="btn" />;
}

// This can be dangerous if not used carefully
function BetterButton({ className, children, ...rest }) {
  // Merge the provided className with our default
  const buttonClass = `btn ${className || ''}`.trim();
  
  // Only spread the remaining props
  return <button className={buttonClass} {...rest}>{children}</button>;
}
                

While convenient, props spreading should be used carefully to avoid passing unnecessary or unknown props to DOM elements, which can cause warnings or unexpected behavior.

JSX Fundamentals

JSX (JavaScript XML) is a syntax extension for JavaScript that looks similar to HTML but allows you to write HTML-like code in your JavaScript. It's a cornerstone of React development.

Basic JSX Syntax


// Simple JSX example
function Welcome() {
  return (
    <div className="welcome-container">
      <h1>Welcome to React!</h1>
      <p>This is a JSX paragraph.</p>
      
      {/* This is a JSX comment */}
      <button type="button">Click Me</button>
    </div>
  );
}

// What it compiles to (simplified)
function Welcome() {
  return React.createElement(
    'div',
    { className: 'welcome-container' },
    React.createElement('h1', null, 'Welcome to React!'),
    React.createElement('p', null, 'This is a JSX paragraph.'),
    React.createElement('button', { type: 'button' }, 'Click Me')
  );
}
                

JSX is transformed into regular JavaScript during the build process. The transformation is typically handled by Babel, which converts the JSX syntax into React.createElement() calls.

JavaScript Expressions in JSX


function UserGreeting() {
  const user = {
    name: 'John Doe',
    age: 28,
    isAdmin: true
  };
  
  const formatName = (user) => {
    return `${user.name} (${user.age})`;
  };
  
  return (
    <div>
      {/* Using variables */}
      <h1>Hello, {user.name}!</h1>
      
      {/* Using functions */}
      <p>Welcome back, {formatName(user)}</p>
      
      {/* Using conditional expressions */}
      <p>Status: {user.isAdmin ? 'Administrator' : 'Regular User'}</p>
      
      {/* Using mathematical expressions */}
      <p>Your age next year will be {user.age + 1}</p>
      
      {/* Using object properties with bracket notation */}
      <p>User info: {user['name']}, {user['age']}</p>
    </div>
  );
}
                

You can embed any valid JavaScript expression inside JSX using curly braces {}. This includes variables, function calls, object properties, and more.

Conditional Rendering in JSX


function ConditionalComponent({ isLoggedIn, hasMessages, messages = [] }) {
  return (
    <div>
      {/* IF condition using && operator */}
      {isLoggedIn && <p>Welcome back!</p>}
      
      {/* IF-ELSE using ternary operator */}
      {isLoggedIn 
        ? <button>Logout</button>
        : <button>Login</button>
      }
      
      {/* Multiple conditions */}
      {isLoggedIn && hasMessages && messages.length > 0 && (
        <div>
          <h2>You have {messages.length} messages</h2>
        </div>
      )}
      
      {/* Assigning JSX to variables */}
      {(() => {
        if (!isLoggedIn) {
          return <p>Please log in to see your dashboard.</p>;
        }
        
        if (!hasMessages) {
          return <p>You have no messages.</p>;
        }
        
        return (
          <ul>
            {messages.map(msg => (
              <li key={msg.id}>{msg.text}</li>
            ))}
          </ul>
        );
      })()}
    </div>
  );
}
                

JSX provides several ways to conditionally render content, including the logical AND operator (&&), ternary expressions, and immediately-invoked function expressions (IIFEs) for more complex logic.

Lists and Keys in JSX


function TodoList({ todos }) {
  return (
    <div>
      <h2>Todo List</h2>
      
      {/* Basic list rendering with unique keys */}
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
      
      {/* List with more complex items */}
      <div className="todo-cards">
        {todos.map((todo) => (
          <div key={todo.id} className="todo-card">
            <h3>{todo.text}</h3>
            <p>Priority: {todo.priority}</p>
            <p>Due: {todo.dueDate}</p>
            
            {/* Nested list */}
            {todo.subtasks && todo.subtasks.length > 0 && (
              <div>
                <h4>Subtasks:</h4>
                <ul>
                  {todo.subtasks.map((subtask) => (
                    <li key={`${todo.id}-${subtask.id}`}>{subtask.text}</li>
                  ))}
                </ul>
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}
                

When rendering lists in React, each item should have a unique "key" prop. Keys help React identify which items have changed, are added, or are removed, which improves performance and prevents bugs when lists are modified.

JSX and HTML Differences


function JSXSpecifics() {
  const handleInputChange = (e) => {
    console.log(e.target.value);
  };
  
  return (
    <div>
      {/* className instead of class */}
      <div className="container">
        Class attribute is "className" in JSX
      </div>
      
      {/* htmlFor instead of for */}
      <label htmlFor="username">Username:</label>
      <input id="username" type="text" />
      
      {/* camelCase attributes */}
      <div tabIndex="0" onClick={() => console.log('clicked')}>
        JSX uses camelCase for attributes
      </div>
      
      {/* Self-closing tags must have a slash */}
      <img src="image.jpg" alt="Example" />
      <br />
      <hr />
      
      {/* style takes an object, not a string */}
      <div style={{ 
        color: 'blue', 
        fontSize: '16px',
        marginTop: '10px'
      }}>
        Inline styles use objects with camelCase properties
      </div>
      
      {/* dangerouslySetInnerHTML for raw HTML */}
      <div dangerouslySetInnerHTML={{ __html: '<strong>Bold text</strong>' }} />
    </div>
  );
}
                

JSX resembles HTML but has several important differences in syntax and behavior. These include using camelCase for attribute names, using className instead of class, and requiring self-closing tags to have a forward slash.

JSX Best Practices in Professional Applications

Organization and Readability

  • Extract complex logic into variables or functions before rendering
  • Use fragments (<></>) to avoid unnecessary wrapper divs
  • Keep components small and focused on a single responsibility
  • Extract repeated JSX into separate components

// Before: Complex logic in JSX
return (
  <div>
    {users.filter(user => user.active).map(user => (
      <div key={user.id} className={user.isAdmin ? 'admin-user' : 'regular-user'}>
        {user.name}
      </div>
    ))}
  </div>
);

// After: Extracted logic and improved readability
function UserList({ users }) {
  const activeUsers = users.filter(user => user.active);
  
  const getUserClassName = (isAdmin) => isAdmin ? 'admin-user' : 'regular-user';
  
  return (
    <>
      {activeUsers.length === 0 ? (
        <p>No active users found.</p>
      ) : (
        activeUsers.map(user => (
          <UserItem 
            key={user.id} 
            name={user.name} 
            className={getUserClassName(user.isAdmin)} 
          />
        ))
      )}
    </>
  );
}

function UserItem({ name, className }) {
  return <div className={className}>{name}</div>;
}
                

Performance Considerations

  • Use key props correctly to help React's reconciliation algorithm
  • Avoid inline function definitions in render, which can cause unnecessary re-renders
  • Memoize complex calculations with useMemo or useCallback

Accessibility

  • Use semantic HTML elements where appropriate
  • Ensure proper ARIA attributes for custom interactive elements
  • Maintain tab order with proper tabIndex values

These practices are followed by companies like Airbnb, Facebook, and Microsoft in their design systems and component libraries to maintain code quality and developer productivity.

Practical Exercise: Building Your First Component Tree

Let's apply what we've learned by building a component tree for a simple blog post display:

Blog Post Component Exercise

Objective: Create a set of React components to display a blog post with comments.

Requirements:

  1. Create the following components:
    • BlogPost (main container)
    • PostHeader (title, author, date)
    • PostContent (content with formatting)
    • PostFooter (tags, share buttons)
    • CommentSection (container for comments)
    • CommentItem (individual comment)
    • CommentForm (form to add new comments)
  2. Use props to pass data between components
  3. Implement proper component composition
  4. Use conditional rendering where appropriate
  5. Handle lists with proper keys

Sample Data:


const blogPostData = {
  id: 1,
  title: "Getting Started with React",
  author: {
    name: "Jane Smith",
    avatar: "https://randomuser.me/api/portraits/women/44.jpg"
  },
  date: "2023-11-15",
  content: `
    <p>React is a popular JavaScript library for building user interfaces...</p>
    <p>In this post, we'll explore the basics of React components and JSX...</p>
    <h2>Why React?</h2>
    <p>React makes it painless to create interactive UIs...</p>
  `,
  tags: ["React", "JavaScript", "Web Development"],
  comments: [
    {
      id: 101,
      author: "John Doe",
      text: "Great introduction to React! Looking forward to more content.",
      date: "2023-11-16",
      likes: 5
    },
    {
      id: 102,
      author: "Alice Johnson",
      text: "I found this very helpful as a beginner. Thanks!",
      date: "2023-11-17",
      likes: 3
    }
  ]
};
                

Bonus Challenge: Add a "like" button to comments that updates the like count when clicked, and implement a feature to sort comments by date or popularity.

Further Resources