React Essentials — Components, JSX, Props, State & MoreEntry 3 of 11
Passing Functions as Props (Child-to-Parent Communication)
A parent passes a function to a child; the child calls it to notify the parent that something happened.
Notes
Because data flows down only, a child can't change parent state directly. Instead, the parent passes a callback (by convention named onSomething). The child calls it, typically in response to a user event, and the parent's logic runs.
Pass the function reference (onClick={onSelect}), not a call (onClick={onSelect()}). The second form runs immediately during render.
What / Why / When
- What
Callbacks as props.
- Why
State stays in the parent while the child stays reusable and unaware of the parent's logic.
- When
A child's user interaction must update state owned higher up.
- Use case
Tab buttons that tell the parent which tab is selected.
Code compare
import { useState, type ReactNode } from "react";type TabButtonProps = { children: ReactNode; isSelected: boolean; onSelect: () => void;};export function TabButton({ children, isSelected, onSelect }: TabButtonProps) { return ( <button className={isSelected ? "active" : undefined} onClick={onSelect}> {children} </button> );}type Topic = "components" | "props" | "state";export function Examples() { const [selectedTopic, setSelectedTopic] = useState<Topic>("components"); return ( <menu> <TabButton isSelected={selectedTopic === "components"} onSelect={() => setSelectedTopic("components")} > Components </TabButton> </menu> );}import { Component, input, output } from "@angular/core";@Component({ selector: "app-tab-button", template: ` <button [class.active]="isSelected()" (click)="selected.emit()"> <ng-content /> </button> `,})export class TabButtonComponent { isSelected = input(false); selected = output<void>();}<!-- Parent template; selectedTopic = signal<Topic>('components') --><app-tab-button [isSelected]="selectedTopic() === 'components'" (selected)="selectedTopic.set('components')"> Components</app-tab-button>Differences and gotchas
output()(v17.3+) replaces@Output()withEventEmitter. The decorator style still works but isn't the modern default.- React passes a function down. Angular emits an event up. Same result, inverted mechanism.
- Angular's style guide discourages the
onprefix for outputs:(selected), not(onSelected).
Quick reference
| Concept | React | Angular |
|---|---|---|
| Declare in child | onSelect: () => void prop | selected = output<void>() |
| Trigger | onSelect() | this.selected.emit() |
| Listen in parent | onSelect={handleSelect} | (selected)="handleSelect()" |