跪拜 Guibai
← All articles
Frontend · JavaScript

Stop Passing React Event Objects to Parent Components

By 默_笙 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Leaking `ChangeEvent` types into parent components is a fast path to brittle, tightly coupled React code. The stateless-child pattern with value-only callbacks keeps parents ignorant of DOM internals, making components swappable and testable without rewriting upstream interfaces.

Summary

Three iterations of a username-editing component expose a common React anti-pattern: passing `React.ChangeEvent` objects from child to parent. Version 1 leaks DOM event types into the parent, coupling it to `<input>` internals. Version 2 gives the child private state and calls `onNameUpdated(newName)` only on submit, so the parent receives a plain string. Version 3 lifts all state to the parent, making the child a pure display component (`UI = fn(props)`) that reports every keystroke via `onEditingNameUpdated(value)` and submits via `onNameUpdated()`. The parent computes `disabled` logic and controls everything through props.

TypeScript's `interface Props` and `React.FC<Props>` catch missing or mis-typed props at compile time instead of runtime. The final pattern keeps data flow unidirectional, eliminates unnecessary re-renders from child-local state, and makes the input component reusable anywhere text editing is needed. An `useEffect` hook with an empty dependency array handles async initial data loading after mount, so the UI appears immediately.

Takeaways
Passing `React.ChangeEvent<HTMLInputElement>` from child to parent forces the parent to know about DOM event types, coupling it to a specific input element.
Version 2 isolates the event inside the child by giving it local state and calling `onNameUpdated(newName)` only on submit, so the parent receives a plain string.
Version 3 lifts all state to the parent and makes the child stateless: every keystroke reports a value through `onEditingNameUpdated`, and submit triggers `onNameUpdated`.
A stateless child that follows `UI = fn(props)` avoids unnecessary re-renders and can be reused for any text-editing scenario.
TypeScript's `interface Props` and `React.FC<Props>` catch missing or wrongly typed props at compile time, preventing runtime `undefined` errors.
`useEffect` with an empty dependency array loads async data after mount, letting the UI render immediately before the data arrives.
Conclusions

The three versions map directly to a developer's growing discomfort with leaking implementation details across component boundaries — what starts as a convenience becomes technical debt the moment the input element changes.

Version 2's local state is a pragmatic middle ground for simple forms, but it breaks the moment the parent needs to know the current draft value for validation or button-disabling logic.

The final pattern treats the child as a controlled input peripheral: it owns no state, only renders props and emits value-change events, which is exactly how native form elements work.

Concepts & terms
React.FC<Props>
A TypeScript type alias for React.FunctionComponent. The generic parameter <Props> constrains the shape of the props object, enabling compile-time checks for missing or incorrect props.
State lifting
Moving state from a child component to its closest common ancestor so multiple children can share it, and so the parent retains control over derived logic like button-disabled conditions.
React.ChangeEvent<T>
The TypeScript type for React's synthetic change event. The generic T specifies the DOM element type (e.g., HTMLInputElement), giving typed access to event.target.value.
useEffect with empty dependency array
A React hook that runs a side-effect function once after the component mounts. The empty array [] tells React to skip re-running the effect on subsequent renders, making it suitable for initial async data fetching.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗