# How to Pass Functions as Props in React Functional Components: Best Practices and Patterns

> Learn how to pass functions as props in React functional components for efficient child-to-parent communication. Discover best practices and patterns for state changes and event handling.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: best-practices
- Published: 2026-02-20

---

**Passing memoized callback functions as props is the canonical pattern for child-to-parent communication in React, enabling unidirectional data flow while preserving performance optimizations.**

When building applications with the facebook/react repository, communicating events from child to parent components requires a specific architectural approach. Passing functions as props to child components maintains React's declarative programming model while ensuring predictable state management across the component tree.

## Why Passing Functions as Props Is the Canonical Pattern

### Enforcing Unidirectional Data Flow

React's architecture enforces a top-down data flow where parents own state and children receive immutable data via props. When a child component needs to trigger a state change in an ancestor, it calls a **callback function** passed through its props rather than mutating state directly. This pattern, known as **lifting state**, ensures a single source of truth and prevents synchronization bugs between components.

### Performance Through Reference Stability

Creating new function instances on every render breaks `React.memo` and `PureComponent` optimizations, forcing unnecessary re-renders. By wrapping callbacks with `useCallback`, you provide a **stable reference** that persists across renders unless dependencies change. This allows memoized children to skip rendering cycles when their props remain identical.

## How React Implements Callback Memoization Under the Hood

The `useCallback` hook stores the callback function and its dependency array within the component's fiber node. In [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js) (lines 135-144), React compares current dependencies against the previous render's dependencies. If they match, React returns the cached function reference; otherwise, it creates a new one. This mechanism ensures that child components receiving these callbacks can efficiently perform shallow equality checks.

Child components that receive callbacks often wrap themselves in `React.memo` to avoid re-rendering when the callback reference is unchanged, as demonstrated in [`packages/react-devtools-shell/src/app/ToDoList/ListItem.js`](https://github.com/facebook/react/blob/main/packages/react-devtools-shell/src/app/ToDoList/ListItem.js) (lines 10-12).

## Implementing Callback Props in Practice

### Basic Parent-Child Communication with useCallback

```jsx
// Parent.jsx
import React, {useState, useCallback, memo} from 'react';
import Child from './Child';

function Parent() {
  const [count, setCount] = useState(0);

  // `increment` maintains stable reference unless setCount changes
  const increment = useCallback(() => setCount(c => c + 1), [setCount]);

  return (
    <div>
      <p>Count: {count}</p>
      <Child onClick={increment} />
    </div>
  );
}
export default Parent;

// Child.jsx
import React, {memo} from 'react';

function Child({onClick}) {
  return <button onClick={onClick}>Add 1</button>;
}
export default memo(Child);

```

In this example, `Child` is wrapped in `memo`, preventing re-renders when the parent updates other state. The `increment` callback remains stable thanks to `useCallback`, satisfying the memo comparison.

### Real-World Pattern: Lifting State in a To-Do List

The React repository's devtools shell demonstrates sophisticated callback patterns in [`packages/react-devtools-shell/src/app/ToDoList/List.js`](https://github.com/facebook/react/blob/main/packages/react-devtools-shell/src/app/ToDoList/List.js). The parent component maintains the items array and passes mutation callbacks to each list item:

```jsx
// List.jsx
import React, {useCallback, useState, Fragment} from 'react';
import ListItem from './ListItem';

export default function List() {
  const [items, setItems] = useState([
    {id: 1, isComplete: true, text: 'First'},
    {id: 2, isComplete: true, text: 'Second'},
    {id: 3, isComplete: false, text: 'Third'},
  ]);

  const removeItem = useCallback(
    item => setItems(prev => prev.filter(i => i !== item)),
    []
  );

  const toggleItem = useCallback(
    item => {
      setItems(prev =>
        prev.map(i =>
          i.id === item.id ? {...i, isComplete: !i.isComplete} : i
        )
      );
    },
    []
  );

  return (
    <Fragment>
      <ul>
        {items.map(item => (
          <ListItem
            key={item.id}
            item={item}
            removeItem={removeItem}
            toggleItem={toggleItem}
          />
        ))}
      </ul>
    </Fragment>
  );
}

```

The child implementation in [`packages/react-devtools-shell/src/app/ToDoList/ListItem.js`](https://github.com/facebook/react/blob/main/packages/react-devtools-shell/src/app/ToDoList/ListItem.js) wraps itself in `React.memo` and uses `useCallback` to memoize its internal event handlers:

```jsx
// ListItem.jsx
import React, {memo, useCallback} from 'react';

function ListItem({item, removeItem, toggleItem}) {
  const handleDelete = useCallback(() => removeItem(item), [item, removeItem]);
  const handleToggle = useCallback(() => toggleItem(item), [item, toggleItem]);

  return (
    <li>
      <button onClick={handleDelete}>🗑</button>
      <input
        type="checkbox"
        checked={item.isComplete}
        onChange={handleToggle}
      />
      {item.text}
    </li>
  );
}
export default memo(ListItem);

```

## Summary

- **Callback props** enable child components to communicate with ancestors without breaking unidirectional data flow
- **useCallback** in [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js) provides reference stability critical for performance optimization
- **React.memo** combined with stable callbacks prevents unnecessary re-renders in child components
- **Lifting state** to common ancestors and passing callbacks down eliminates duplicate state sources
- The patterns demonstrated in `packages/react-devtools-shell/src/app/ToDoList/` provide production-ready examples of event handling architecture

## Frequently Asked Questions

### Should I always use useCallback when passing functions as props?

No. While `useCallback` is essential for optimizing performance in memoized child components or when the function is a dependency of other hooks, it adds overhead. For non-memoized children or functions passed directly to DOM elements, the overhead may not justify the benefit. Reserve `useCallback` for components wrapped in `memo` or when the function is used in `useEffect` dependency arrays.

### What happens if I pass an inline function instead of a memoized callback?

Passing an inline function like `onClick={() => handleClick()}` creates a new function reference on every render. If the child component uses `React.memo` or extends `PureComponent`, this new reference triggers a re-render despite identical behavior. According to the implementation in [`ReactHooks.js`](https://github.com/facebook/react/blob/main/ReactHooks.js), this bypasses the optimization benefits of stable references.

### How do I prevent unnecessary re-renders when passing callbacks to children?

Wrap the child component with `React.memo` and define the callback using `useCallback` with a stable dependency array. As shown in [`packages/react-devtools-shell/src/app/ToDoList/ListItem.js`](https://github.com/facebook/react/blob/main/packages/react-devtools-shell/src/app/ToDoList/ListItem.js), this combination ensures React's reconciliation algorithm skips the component when props remain shallowly equal.

### Is it better to lift state up or use Context for deeply nested callbacks?

For deeply nested component trees where intermediate components don't use the callback themselves, React Context with `useContext` may reduce prop drilling. However, Context does not automatically prevent re-renders—consumers still re-render when context values change. For performance-critical applications, lifting state and passing callbacks explicitly often provides more predictable optimization opportunities than Context alone.