React Props Complete Guide: Passing Data and Destructuring

Props are how React components communicate with each other. They’re like function parameters—data passed from a parent component to a child component. Props are immutable and flow downward through the component tree.

Understanding props is fundamental to React. Every component you build will receive and send props, making this knowledge essential for writing effective React code.

Core Concept:
Props enable components to be reusable by allowing data to be passed in from parent components. A single component can render differently based on different props.

Basic Prop Passing

Simple Props Example

// Parent Component
import { Greeting } from ‘./Greeting’;export function App() {
return (
<>
<Greeting name=“Alice” age={25} />
<Greeting name=“Bob” age={30} />
</>
);
}

// Child Component
export function Greeting(props) {
return <h1>Hello {props.name}, age {props.age}</h1>;
}

String vs Expression Props

// String values don’t need braces
<Button label=“Click Me” />// JavaScript expressions need braces
<Button count={5} />
<Button disabled={isLoading} />
<Button callback={() => console.log(‘clicked’)} />
<Button size={10 * 2} />

// Objects need double braces
<Button style={{ color: ‘red’, fontSize: ’16px’ }} />

Multiple Props

export function Card(props) {
return (
<div style={{ border: ‘1px solid #ccc’, padding: ’20px’ }}>
<h2>{props.title}</h2>
<p>{props.description}</p>
<img src={props.imageUrl} alt={props.title} />
<button onClick={props.onAction}>
{props.buttonText}
</button>
</div>
);
}// Usage
<Card
title=“Product Name”
description=“Great product”
imageUrl=“https://…”
onAction={() => console.log(‘Action clicked’)}
buttonText=“Buy Now”
/>

Destructuring Patterns

Basic Destructuring

// Without destructuring (verbose)
function Greeting(props) {
return <h1>Hello {props.name}</h1>;
}// With destructuring (cleaner)
function Greeting({ name }) {
return <h1>Hello {name}</h1>;
}

// Multiple props
function UserProfile({ name, email, age, city }) {
return (
<div>
<h1>{name}</h1>
<p>Email: {email}</p>
<p>Age: {age}</p>
<p>City: {city}</p>
</div>
);
}

Renaming Properties

// Rename prop to avoid conflicts
function Component({
className: customClass,
onClick: handleClick
}) {
return <button className={customClass} onClick={handleClick}>Click</button>;
}// Usage with renamed props
<Component className=“btn-primary” onClick={() => {}} />

Nested Destructuring

// Destructure nested objects
function UserCard({ user: { name, address: { city, country } } }) {
return (
<div>
<h2>{name}</h2>
<p>From {city}, {country}</p>
</div>
);
}// Usage
<UserCard user={{
name: ‘John’,
address: { city: ‘NYC’, country: ‘USA’ }
}} />

Array Destructuring in Props

// Destructure array props
function ColorBox({ colors: [primary, secondary] }) {
return (
<div>
<div style={{ backgroundColor: primary }}>Primary</div>
<div style={{ backgroundColor: secondary }}>Secondary</div>
</div>
);
}// Usage
<ColorBox colors={[‘red’, ‘blue’]} />

Default Values and Optional Props

Function Parameter Defaults

// Using default parameters
function Button({
text = ‘Click Me’,
disabled = false,
size = ‘medium’
}) {
return (
<button disabled={disabled} className={`btn-${size}`}>
{text}
</button>
);
}// These all work without errors
<Button />
<Button text=“Submit” />
<Button text=“Delete” disabled={true} />

Default Props Pattern

function Card({ title, description, imageUrl, priority }) {
return (
<div className={`card card-priority-${priority}`}>
<img src={imageUrl} alt={title} />
<h2>{title}</h2>
<p>{description}</p>
</div>
);
}// Set defaults after component definition
Card.defaultProps = {
priority: ‘normal’,
description: ‘No description provided’,
imageUrl: ‘/placeholder.png’
};

export default Card;

Optional Chaining for Safety

// Safely access nested properties
function UserInfo({ user }) {
return (
<div>
// Without optional chaining – can crash
<p>City: {user.address.city}</p>// With optional chaining – safe
<p>City: {user?.address?.city}</p>

// With fallback
<p>City: {user?.address?.city ?? ‘Unknown’}</p>
</div>
);
}

Working with Children Props

Children Basics

// children is a special built-in prop
function Modal({ title, children }) {
return (
<div className=“modal”>
<h2>{title}</h2>
<div className=“modal-content”>
{children}
</div>
</div>
);
}// Usage – anything between tags becomes children
<Modal title=“Delete Confirmation”>
<p>Are you sure you want to delete?</p>
<button>Yes</button>
<button>No</button>
</Modal>

Children as Function (Render Props)

// Children can be a function
function DataFetcher({ url, children }) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);React.useEffect(() => {
fetch(url)
.then(r => r.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);

// children is a function that receives data
return children(data, loading);
}

// Usage with function as child
<DataFetcher url=“/api/users”>
{(data, loading) => (
loading ? <p>Loading…</p> : <ul>{data.map(…)}</ul>
)}
</DataFetcher>

Multiple Named Slots

// Component with multiple content slots
function Layout({ header, sidebar, children, footer }) {
return (
<div className=“layout”>
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
<footer>{footer}</footer>
</div>
);
}// Usage with named slots
<Layout
header={<h1>My Site</h1>}
sidebar={<Nav />}
footer={<p>© 2025</p>}
>
<p>Main content goes here</p>
</Layout>

Spread Operator Patterns

Forwarding Props

// Spread props to pass them all down
function CustomButton({ variant, …buttonProps }) {
return (
<button
className={`btn btn-${variant}`}
{…buttonProps}
>
{buttonProps.children}
</button>
);
}// All HTML attributes are automatically passed
<CustomButton
variant=“primary”
disabled={false}
onClick={() => {}}
type=“submit”
className=“my-custom-class”
>
Submit Form
</CustomButton>

Extracting and Spreading

// Extract specific props, spread the rest
function Card({
title,
description,
…containerProps
}) {
return (
<div {…containerProps}>
<h2>{title}</h2>
<p>{description}</p>
</div>
);
}// className, onClick, etc. are spread onto the div
<Card
title=“Title”
description=“Desc”
className=“card-primary”
onClick={() => {}}
id=“card-1”
/>

Merging Props Objects

// Create objects with spread syntax
const baseProps = {
className: ‘btn’,
disabled: false,
onClick: handleClick
};const additionalProps = {
className: ‘btn-primary’,
type: ‘submit’
};

// Merge: additionalProps overrides baseProps
const finalProps = { …baseProps, …additionalProps };

<button {…finalProps}>Submit</button>

Prop Validation with PropTypes

Basic PropTypes Setup

import PropTypes from ‘prop-types’;function User({ name, age, email, isActive }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
<p>Status: {isActive ? ‘Active’ : ‘Inactive’}</p>
</div>
);
}

// Define expected prop types
User.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
email: PropTypes.string,
isActive: PropTypes.bool
};

export default User;

Common PropTypes Validators

MyComponent.propTypes = {
// Primitive types
string: PropTypes.string,
number: PropTypes.number,
boolean: PropTypes.bool,
function: PropTypes.func,
object: PropTypes.object,
array: PropTypes.array,
any: PropTypes.any,// Specific values (enum)
color: PropTypes.oneOf([‘red’, ‘green’, ‘blue’]),

// Multiple types
flexible: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

// Complex types
users: PropTypes.arrayOf(PropTypes.object),
settings: PropTypes.shape({
theme: PropTypes.string,
fontSize: PropTypes.number
}),

// Required
required: PropTypes.string.isRequired,

// Custom validation
customProp: (props, propName) => {
if (props[propName] < 0) {
return new Error(‘Value must be positive’);
}
}
};

TypeScript Props

Interface-Based Props

interface UserProps {
name: string;
age: number;
email?: string; // optional with ?
active: boolean;
}function UserProfile({ name, age, email, active }: UserProps) {
return (
<div>
<h1>{name}</h1>
<p>Age: {age}</p>
{email && <p>Email: {email}</p>}
<p>{active ? ‘Active’ : ‘Inactive’}</p>
</div>
);
}

Type-Based Props with Union Types

type ButtonVariant = ‘primary’ | ‘secondary’ | ‘danger’;
type ButtonSize = ‘small’ | ‘medium’ | ‘large’;interface ButtonProps {
variant?: ButtonVariant;
size?: ButtonSize;
disabled?: boolean;
onClick: () => void;
children: React.ReactNode;
}

function Button({
variant = ‘primary’,
size = ‘medium’,
…props
}: ButtonProps) {
return (
<button className={`btn-${variant} btn-${size}`} {…props}>
{props.children}
</button>
);
}

Generic Props

interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string | number;
}function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>
{renderItem(item)}
</li>
))}
</ul>
);
}

// Usage with type inference
const users: User[] = […];
<List
items={users}
renderItem={(user) => <>{user.name} ({user.age})</>}
keyExtractor={(user) => user.id}
/>

Props Best Practices

Naming Conventions

// ✅ Good: Clear, descriptive names
<Button onClick={handleSubmit} disabled={isLoading}>Submit</Button>
<UserCard user={userData} onUserUpdate={handleUserUpdate} />// ❌ Avoid: Vague or abbreviated names
<Button onClick={handle} dis={load}>Submit</Button>
<UserCard u={data} onU={update} />

// ✅ Event handlers: on[Event]
<Input onChange={handleChange} onBlur={handleBlur} onFocus={handleFocus} />
<Card onDelete={handleDelete} onEdit={handleEdit} />

Keep Components Simple

// ❌ Too many props – component does too much
<ComplexComponent
title={title}
description={description}
image={image}
onClick={onClick}
onHover={onHover}
onFocus={onFocus}
theme={theme}
variant={variant}
size={size}
disabled={disabled}
loading={loading}
error={error}
success={success}
// … 10 more props
/>// ✅ Better: Split into smaller components
<Card title={title} image={image}>
<CardDescription text={description} />
<CardActions onClick={onClick} onHover={onHover} />
</Card>

Avoid Prop Drilling

// ❌ Prop drilling through many levels
<GrandParent user={user}>
<Parent user={user}>
<Child user={user}>
<GrandChild user={user} />
</Child>
</Parent>
</GrandParent>// ✅ Use Context API for deep nesting
<UserContext.Provider value={user}>
<GrandParent>
<Parent>
<Child>
<GrandChild /> {/* uses useContext(UserContext) */}
</Child>
</Parent>
</GrandParent>
</UserContext.Provider>

Document Your Props

/**
* Button component for user interactions
* @param {string} variant – Button style (primary, secondary, danger)
* @param {string} size – Button size (small, medium, large)
* @param {boolean} disabled – Disable button
* @param {function} onClick – Click handler
* @param {ReactNode} children – Button content
*/

export function Button({
variant = ‘primary’,
size = ‘medium’,
disabled = false,
onClick,
children
}) {
// implementation
}
✅ Props Mastery: Use props to make components reusable and flexible. Destructure for cleaner code, use TypeScript for type safety, and split large prop lists into smaller components. Well-designed props make components easy to understand and maintain.
© 2025 JavaScript UX. React Props Complete Guide Tutorial.

Leave a Reply

Your email address will not be published. Required fields are marked *