How to Call a Parent Method from a Child Component in React
Advertisement
🧩 Scenario
Architecture Walkthrough
One-Way Data Flow and Callbacks
React enforces one-way data flow: data flows from parent to child via props, and events flow from child to parent via callback props. A child component cannot reach into its parent and modify state directly. Instead, the parent passes a function as a prop. The child calls that function, optionally passing data as arguments. The parent receives the call and updates its own state.
This design keeps data ownership explicit. The state always lives in the component that controls it. The child is a controlled component that signals intent to the parent but does not manage the shared data itself.
TypeScript Callback Prop Typing
In TypeScript, callback props are typed as function signatures. A callback that receives an item ID and returns nothing is (id: string) => void. A callback that receives a form object is (data: FormData) => void. Typing these explicitly ensures the child passes the right arguments and the parent handles the right shape.
Reference Stability with useCallback
When a parent passes a callback defined as an inline arrow function in JSX, a new function reference is created on every parent render. If the child is wrapped in React.memo, this new reference causes the child to re-render even when the actual behavior of the callback has not changed. Wrapping the callback in useCallback with appropriate dependencies stabilizes the reference: the same function object is reused across renders unless the dependencies change.
Key Code Explained
// Basic pattern: parent passes callback, child calls it
interface DeleteButtonProps {
itemId: string;
onDelete: (id: string) => void; // typed callback prop
}
function DeleteButton({ itemId, onDelete }: DeleteButtonProps) {
return (
<button
className="btn-destructive"
onClick={() => onDelete(itemId)} // calls parent's handler with data
>
Delete
</button>
);
}
function ItemList() {
const [items, setItems] = useState<Item[]>(initialItems);
// useCallback: stable reference across renders
const handleDelete = useCallback((id: string) => {
setItems((prev) => prev.filter((item) => item.id !== id));
}, []); // no deps: setItems is stable, filter logic has no external deps
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}
<DeleteButton itemId={item.id} onDelete={handleDelete} />
</li>
))}
</ul>
);
}
// Passing a complex object from child to parent
interface SearchFilters {
query: string;
category: string;
minPrice: number;
}
interface FilterPanelProps {
onFiltersChange: (filters: SearchFilters) => void;
}
function FilterPanel({ onFiltersChange }: FilterPanelProps) {
const [query, setQuery] = useState('');
const [category, setCategory] = useState('all');
const [minPrice, setMinPrice] = useState(0);
const handleApply = () => {
// Pass all filter data as a typed object back to the parent
onFiltersChange({ query, category, minPrice });
};
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<select value={category} onChange={(e) => setCategory(e.target.value)}>
<option value="all">All</option>
<option value="electronics">Electronics</option>
</select>
<button onClick={handleApply}>Apply Filters</button>
</div>
);
}
function SearchPage() {
const [filters, setFilters] = useState<SearchFilters>({
query: '',
category: 'all',
minPrice: 0,
});
// Parent receives the filters object and updates its own state
const handleFiltersChange = useCallback((newFilters: SearchFilters) => {
setFilters(newFilters);
}, []);
return (
<div>
<FilterPanel onFiltersChange={handleFiltersChange} />
<ResultList filters={filters} />
</div>
);
}
The useCallback with an empty dependency array on handleDelete is safe here because the function only calls setItems (which is stable across renders from useState) and uses the functional updater form (prev => ...). If the callback closed over a state or prop value directly (like items instead of using the updater form), it would need that value in the dependency array.
Tradeoffs
| Communication method | Direction | When to use |
|---|---|---|
| Callback props | Child to parent | 1-2 levels deep, localized component communication |
| Prop drilling callbacks | Child to ancestor | Works but creates coupling; avoid beyond 2-3 levels |
| Context | Any depth | Callbacks needed by many unrelated components |
| State manager (Zustand) | Any depth | Complex shared actions across the app |
What Interviewers Actually Check
- Whether you know the callback prop pattern and can implement it correctly
- Whether you type the callback prop in TypeScript
- Whether you know that
useCallbackis needed to stabilize the reference forReact.memo - Whether you know that inline arrow functions in JSX create new references on every render
- Whether you know when to switch from callback props to context for deeply nested communication
Follow-Up Questions
- How would you call a child's imperative method from a parent using
useImperativeHandleandforwardRef? - If a callback is passed three levels deep (grandparent to grandchild), how does context solve the prop-drilling problem?
- When would you use an event bus or a global event emitter instead of callback props?
- How do you handle the case where a callback should only be callable once (a one-time confirmation dialog)?
- How does the callback pattern interact with
React.memo: why does a new function reference break memoization?
Common Candidate Mistakes
- Trying to access the parent state directly from the child (not possible in React's model)
- Not using
useCallbackwhen the child is wrapped inReact.memo, silently breaking memoization - Not typing the callback prop, losing type safety on the arguments the child passes back
- Prop drilling callbacks through 4+ levels when context or a state manager would be cleaner
- Defining the callback inline in JSX (
onDelete={() => handleDelete(id)}) which creates a new reference on every render
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you pass a callback from a parent to a child and call it from the child?
- Can you type a callback prop in TypeScript with the correct signature?
- Can you explain why
useCallbackmatters for stabilizing callback references for memoized children? - Can you explain the difference between passing data down (props) and passing events up (callbacks)?
- Can you describe when to move from callback props to context for deeply nested component communication?
Summary
Child-to-parent communication in React is accomplished by passing callback functions from parent to child as props. The child calls the callback with any data it wants to pass back; the parent handles the call and updates its own state. The child never accesses or modifies parent state directly.
In TypeScript, callback props are typed as function signatures in the props interface, which enforces that the child passes the correct argument types. This is the most common pattern for localized component interactions: button clicks, form submissions, list item selections, and modal close events.
For memoized children (wrapped in React.memo), the callback reference must be stable. An inline arrow function in JSX creates a new reference on every parent render, causing the child to re-render regardless of React.memo. Wrapping the callback in useCallback with the correct dependency array stabilizes the reference. When callback props must travel through three or more component layers, context or a lightweight state manager is cleaner than prop drilling.
Can a child component directly modify parent state?
No. A child cannot access or modify the parent state directly. It can only call a callback function the parent passed as a prop, and the parent decides how to update its own state in response.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement