← Back to Course Overview

Module 2 - Component Composition

JavaScript Modules

Learn about JavaScript modules and how they help organize React applications.

JavaScript modules are a way to organize code into separate files, making it easier to maintain and reuse. In modern JavaScript, modules use the import and export syntax to share functionality between files.

A module is simply a JavaScript file that exports some functionality (variables, functions, classes, etc.) that other files can import. Modules help prevent polluting the global namespace and make dependencies between parts of your code explicit.

There are two types of exports:

Example of a module exporting functionality:

// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export default function multiply(a, b) {
  return a * b;
}

Example of importing from a module:

// app.js
import multiply, { add, subtract } from './math.js';

console.log(add(2, 3));        // 5
console.log(subtract(5, 2));   // 3
console.log(multiply(2, 3));   // 6

Importing and Exporting Components

Master the techniques of importing and exporting React components.

In React applications, each component is typically defined in its own file and exported so it can be imported and used by other components. This modular approach keeps code organized and reusable.

When creating a React component, you'll typically export it at the end of the file, and then import it where needed:

// Button.js
import React from 'react';

function Button({ text, onClick }) {
  return (
    <button onClick={onClick}>
      {text}
    </button>
  );
}

export default Button;

Then in another file, you can import and use this component:

// App.js
import React from 'react';
import Button from './Button';

function App() {
  const handleClick = () => {
    alert('Button clicked!');
  };

  return (
    <div>
      <h1>My App</h1>
      <Button text="Click Me" onClick={handleClick} />
    </div>
  );
}

export default App;

Passing Data Using Props

Learn how to pass data between components using props.

Props (short for "properties") are a way to pass data from a parent component to a child component in React. Props are read-only and help make your components reusable and dynamic.

Props are passed to components as attributes in JSX, similar to HTML attributes. Inside the component, props are received as an object parameter in the component function.

Here's how props work:

// Parent component passing props
function ParentComponent() {
  const user = {
    name: "Alice",
    age: 28,
    role: "Developer"
  };

  return (
    <div>
      <h1>User Profile</h1>
      <UserCard 
        name={user.name} 
        age={user.age} 
        role={user.role} 
      />
    </div>
  );
}

// Child component receiving props
function UserCard(props) {
  return (
    <div className="user-card">
      <h2>{props.name}</h2>
      <p>Age: {props.age}</p>
      <p>Role: {props.role}</p>
    </div>
  );
}

// Alternatively, you can use destructuring for cleaner code
function UserCard({ name, age, role }) {
  return (
    <div className="user-card">
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Role: {role}</p>
    </div>
  );
}

Props are immutable (the child component cannot change them), which helps maintain a unidirectional data flow in your React applications. If you need to modify data, you would typically lift the state up to a parent component.

Composing React Components

Learn how to compose complex UIs using multiple React components.

Component composition is a fundamental concept in React that allows you to build complex UIs by combining smaller, reusable components. This approach makes your code more maintainable, testable, and easier to reason about.

The main benefit of component composition is that it lets you split the UI into independent, reusable pieces. Each component can focus on a specific part of the UI, making the codebase easier to manage as it grows.

Here's an example of component composition:

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

// Content component
function Content({ posts }) {
  return (
    <main>
      <FeaturedPost post={posts[0]} />
      <PostList posts={posts.slice(1)} />
      <Sidebar />
    </main>
  );
}

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

// App component combining everything
function App() {
  const posts = [...]; // Some data
  
  return (
    <div className="app">
      <Header />
      <Content posts={posts} />
      <Footer />
    </div>
  );
}

React also supports containment, where components don't know their children ahead of time. You can use the special children prop to pass child elements directly:

function Card({ title, children }) {
  return (
    <div className="card">
      <div className="card-header">{title}</div>
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

// Usage
<Card title="Welcome">
  <p>This is some content inside the card.</p>
  <button>Click me</button>
</Card>

Passing Callbacks Using Props

Learn how to pass functions as props to enable component communication.

In React, passing callback functions as props is a common pattern for communication between parent and child components. This allows child components to send data back to the parent or trigger actions in the parent component.

This pattern is especially useful because React's data flow is unidirectional (top-down). While props can only be passed from parent to child, callback functions enable communication in the opposite direction.

Here's an example of passing a callback function as a prop:

// Parent component
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', completed: false },
    { id: 2, text: 'Build an app', completed: false }
  ]);
  
  // Callback function to be passed to child
  const toggleTodo = (id) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };
  
  return (
    <div>
      <h1>Todo List</h1>
      {todos.map(todo => (
        <TodoItem 
          key={todo.id} 
          todo={todo} 
          onToggle={toggleTodo} // Passing the callback
        />
      ))}
    </div>
  );
}

// Child component using the callback
function TodoItem({ todo, onToggle }) {
  return (
    <div className="todo-item">
      <input 
        type="checkbox" 
        checked={todo.completed} 
        onChange={() => onToggle(todo.id)} // Using the callback
      />
      <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
    </div>
  );
}

In this example, when a user checks or unchecks a todo item, the onChange event in the child component calls the onToggle callback with the todo's ID. This triggers the toggleTodo function in the parent, which updates the state. The updated state is then passed back down to the child components, completing the data flow cycle.

Module Project

Apply your knowledge by building a React application that demonstrates component composition and props.

Additional Resources