Functional Components & Props
Workplace Context
You are working on a large application with many UI elements like buttons, cards, user avatars, and form fields. Instead of writing the same HTML and CSS repeatedly, developers create reusable building blocks called components. This lesson explores how to create these blocks using React’s functional components. You will also learn how props allow you to customize these components by passing data into them, similar to how you might configure different instances of a reusable tool. This practice is fundamental to building scalable and maintainable applications, allowing teams to build consistent UIs efficiently.
Learning Objectives
By the end of this lesson, learners will be able to:
- Create functional React components using TypeScript (
.tsx
files). - Explain the convention of capitalizing component names.
- Export components from files and import them into other files.
- Compose components by nesting them within one another.
- Explain the concept of props and unidirectional data flow (parent to child).
- Pass data (strings, numbers, booleans, objects, arrays, functions) from a parent component to a child component using props.
- Define the expected shape and types of props for a component using TypeScript interfaces.
- Access props within a functional component.
- Use prop destructuring for cleaner access to prop values.
- Utilize the special
children
prop to pass content into a component.
Functional Components
In modern React, the primary way to create components is by writing JavaScript/TypeScript functions. These functions accept data as input (props) and return React elements (typically written using JSX) that describe what should appear on the screen.
Key Characteristics:
- Just Functions: They are regular JavaScript/TypeScript functions.
- Input (Props): They receive data through an optional first argument, conventionally named
props
. - Output (JSX): They must return a single React element (JSX structure,
null
, or a Fragment) describing the UI. - Naming Convention: Component names must start with a capital letter (e.g.,
MyComponent
,UserProfile
,Button
). This convention allows React to distinguish between custom components and regular HTML tags (e.g.,<MyComponent />
vs.<div>
).
Basic Example:
// src/components/Greeting.tsx
import React from 'react'; // Import React (needed for JSX processing, though less explicit now)
// Define the functional component
function Greeting() {
const message = "Hello from the Greeting component!";
return <h1>{message}</h1>;
}
// Export the component to make it available for use elsewhere
export default Greeting;
This component, Greeting
, simply renders an <h1>
tag with a predefined message.
Exporting and Importing Components
To create reusable components, you need to define them in their own files and then make them available to other parts of your application. We use standard JavaScript/TypeScript module syntax (export
and import
).
-
export default ComponentName
: Used when a file primarily exports one main thing (like a component). You can import it with any name, although using the original name is conventional.// In src/components/Greeting.tsx export default Greeting; // In src/App.tsx import MyCustomGreeting from './components/Greeting'; // Can use any name // or (more common) import Greeting from './components/Greeting'; // Use the original name
-
export { ComponentName, AnotherComponent }
: Used for exporting multiple items (named exports) from a single file. Imports must use the exact names within curly braces.// In src/components/Layout.tsx export function Header() { /* ... */ } export function Footer() { /* ... */ } // In src/App.tsx import { Header, Footer } from './components/Layout';
File Structure Convention:
It is common practice to create a src/components
directory to store reusable components, often with each component in its own file (e.g., Button.tsx
, Card.tsx
).
Component Composition
Components are designed to be composed — meaning you can use components inside other components. This allows you to build complex UIs from smaller, simpler pieces.
function Greeting() {
return <h1>Hello World!</h1>;
}
export default Greeting;
function WelcomeMessage() {
return <p>Welcome to our application.</p>;
}
export default WelcomeMessage;
import Greeting from './components/Greeting';
import WelcomeMessage from './components/WelcomeMessage';
import './App.css'; // Assuming some basic styling
function App() {
// Here, App component is composing Greeting and WelcomeMessage
return (
<div className="container">
<Greeting /> {/* Rendering the Greeting component */}
<WelcomeMessage /> {/* Rendering the WelcomeMessage component */}
<p>This is the main App component.</p>
</div>
);
}
export default App;
In this example, the App
component renders instances of Greeting
and WelcomeMessage
.
Introduction to Props
Components often need to be dynamic and display different data. Props (short for “properties”) are how you pass data from a parent component to a child component.
Think of props like arguments passed to a function. The parent component provides the “arguments” (props) when it renders the child component, and the child component receives them.
Key Principles:
- Unidirectional Data Flow: Data flows in one direction: down from parent to child. A child component cannot directly modify the props it receives from its parent. Props are read-only within the child component.
- Customization: Props make components reusable and configurable. A single
Button
component can render buttons with different text, colors, or actions based on the props it receives.
Passing and Receiving Props
You pass props as attributes in the JSX tag when rendering the child component. Explore the UserProfile
component and the App.tsx
file below.
Explanation:
- Interface (
UserProfileProps
): InUserProfile.tsx
, we define a TypeScriptinterface
that describes the shape of theprops
object this component expects. It lists each prop name and its expected data type (string
,number
,boolean
,string[]
for an array of strings,object
,() => void
for a function that takes no arguments and returns nothing). - Typing Props: The
UserProfile
function signature is(props: UserProfileProps)
. This tells TypeScript that theprops
argument must conform to theUserProfileProps
interface, providing autocompletion and compile-time error checking. - Passing Props: In
App.tsx
, when rendering<UserProfile ... />
, we pass props like HTML attributes.- Strings can be passed with double quotes (
username="Alice"
). - Crucially: Non-string values (numbers, booleans, objects, arrays, variables, function calls) must be enclosed in curly braces
{}
(e.g.,age={30}
,isActive={true}
,hobbies={user1Hobbies}
).
- Strings can be passed with double quotes (
- Accessing Props: Inside
UserProfile
, we access the passed data via theprops
object (e.g.,props.username
,props.age
).
Typing Props with TypeScript Interfaces
Using TypeScript interfaces for props is a best practice in modern React development:
- Self-Documentation: The interface clearly defines what data the component needs.
- Type Safety: TypeScript checks that the parent component passes the correct types of data and that the child component uses them correctly. This catches many potential errors during development rather than at runtime.
- Autocompletion: Code editors provide better autocompletion for props when types are defined.
Convention: Name the props interface ComponentNameProps
(e.g., UserProfileProps
, ButtonProps
).
Prop Destructuring
Accessing props via props.propertyName
repeatedly can be verbose. Destructuring is a JavaScript feature that lets you unpack values from objects (or arrays) into distinct variables. It makes code cleaner.
You can destructure props directly in the function signature:
import React from 'react';
interface UserProfileProps {
username: string;
age: number;
isActive: boolean;
// ... other props
}
// Destructure props directly in the function parameters
function UserProfile({ username, age, isActive }: UserProfileProps) {
return (
<div className="user-profile">
{/* Now access them directly as variables */}
<h2>Username: {username}</h2>
<p>Age: {age}</p>
<p>Status: {isActive ? 'Active' : 'Inactive'}</p>
{/* ... */}
</div>
);
}
export default UserProfile;
This is the most common and recommended way to handle props in functional components.
The children
Prop
There is a special, implicit prop called children
. It allows a parent component to pass elements between the opening and closing tags of the child component.
This is commonly used for creating layout or wrapper components.
Explanation:
- Typing
children
: InCardProps
, we definechildren
with the typeReact.ReactNode
. This is the standard type for thechildren
prop. - Passing
children
: InApp.tsx
, whatever JSX is placed inside the<Card>...</Card>
tags (paragraphs, other components, etc.) becomes the value ofprops.children
in theCard
component. - Rendering
children
: InsideCard
, we render{children}
wherever we want the passed content to appear.
Activity 1: Create a Reusable Button Component
- Create a new file:
src/components/Button.tsx
. - Define a
ButtonProps
interface:text
:string
(the text to display on the button)type?
:'button' | 'submit' | 'reset'
(optional, defaults to ‘button’) - uses union types.onClick?
:() => void
(optional click handler function)disabled?
:boolean
(optional, to disable the button)
- Create the
Button
functional component that accepts these props (use destructuring). - Render a standard HTML
<button>
element.- Display
text
inside the button. - Set the
type
attribute (defaulting to'button'
if not provided). - Attach the
onClick
handler if provided. - Set the
disabled
attribute ifdisabled
is true.
- Display
- Export the
Button
component. - In
src/App.tsx
, import and render several instances of yourButton
with different props (different text, one disabled, one with anonClick
handler that shows an alert).
Activity 2: Create a Wrapper Component using children
- Create a component
src/components/Section.tsx
. - Define
SectionProps
with:title
:string
children
:React.ReactNode
- The
Section
component should render an HTML<section>
tag. - Inside the
<section>
, render an<h2>
with thetitle
prop. - Below the
<h2>
, render thechildren
. - Add some basic inline styling or a CSS class for visual distinction (e.g., a border).
- In
App.tsx
, use theSection
component to wrap different content areas (e.g., wrap the buttons from Activity 1, wrap some plain text).
Lab 1 Introduction: Component Creation & Props
The first graded lab will require you to apply the concepts from this lesson.
Scenario: Your team is building a component library for your company’s internal applications. Your task is to create a set of reusable UI components according to provided specifications. These components will need to be configurable via props to suit various use cases.
Task Overview: You will typically be asked to build components like AlertBox
, UserProfileCard
, ProductDisplay
, etc. You will need to:
- Define the component structure using JSX.
- Define appropriate TypeScript interfaces for the props each component accepts.
- Implement the components to render correctly based on the props they receive.
- Use prop destructuring and handle
children
where appropriate. - Commit your work frequently using Git best practices.
Knowledge Check
What is the naming convention for React functional components?
- Select an answer to view feedback.
How is data typically passed from a parent component to a child component in React?
- Select an answer to view feedback.
In TypeScript, what is the recommended way to define the expected shape and types of props for a component?
- Select an answer to view feedback.
What is prop destructuring in a functional component?
- Select an answer to view feedback.
What is the purpose of the special 'children' prop?
- Select an answer to view feedback.
Summary
In this lesson, you learned how to create reusable UI elements using functional components in React. We emphasized the importance of the PascalCase naming convention and how to use standard export
/import
syntax. You explored component composition by nesting components. Most importantly, you learned about props as the mechanism for passing data down the component tree (unidirectional data flow), how to define prop types using TypeScript interfaces for better code quality, how to use destructuring for cleaner access, and how the special children
prop enables flexible component composition. These concepts are foundational for building any React application.
References
- React Docs:
- MDN: Destructuring assignment
Additional Resources
- React TypeScript Cheatsheet (Community-maintained examples)
- Composition vs Inheritance (Discusses React’s preference for composition, relevant to
children
prop)