How to Prevent Prop Overriding with React Spread Props
To ensure specific props aren't overridden by react spread props, place them after the spread operator in JSX or destructure them out of the spread object beforehand.
When working with the facebook/react codebase, understanding how react spread props merge with explicit attributes helps you build more predictable components. React follows standard JavaScript object-spread semantics where the order of properties determines which values take precedence in the final props object.
How React Processes Spread Props Internally
React constructs the final props object according to standard JavaScript merge rules. In packages/react/src/jsx/ReactJSXElement.js, the JSX runtime creates a fresh props object and copies properties from the config argument, skipping only reserved keys like key, ref, __self, and __source (lines 335-344).
When using cloneElement, the implementation iterates through the config argument after the original props, meaning config values overwrite existing ones (lines 80-84 and 102-108). This internal behavior confirms that later entries always win—a property placed after a spread overwrites values from that spread, while properties placed before the spread remain safe from override.
Two Methods to Protect Specific Props
Place Explicit Props After the Spread
The simplest way to lock in a specific value is to write it after the spread operator in your JSX. Because React processes props left-to-right, any explicit attribute positioned after {...spreadProps} guarantees that value takes precedence.
// className in extraProps is ignored; "my-button" wins
<Button {...extraProps} className="my-button" />
// onClick from incomingProps is overwritten by handleClick
<Component {...incomingProps} onClick={handleClick} />
This pattern works for any prop type, including event handlers, styles, and IDs.
Destructure to Remove Keys Before Spreading
For scenarios where you need to ensure a prop never comes from the spread object, destructure it out first. This approach is essential when the spread source might contain unwanted keys that could break styling or event handling.
// Extract style and className, then spread the rest
const { style, className, ...rest } = extraProps;
<Component
{...rest}
className="base-style"
style={defaultStyle}
/>
Alternatively, filter the object programmatically when dealing with dynamic keys:
const safeProps = Object.fromEntries(
Object.entries(extraProps).filter(([key]) =>
!['className', 'style'].includes(key)
)
);
<Component {...safeProps} className="protected" />
By removing the protected keys from the spread source, you eliminate the possibility of them clashing with your explicit values.
Practical Implementation Examples
Preventing ID collisions:
// Ensures the component always has the specific ID regardless of incoming props
<Modal {...props} id="unique-modal-instance" />
Isolating event handlers:
const { onClick, onSubmit, ...safeProps } = userProvidedProps;
<Form
{...safeProps}
onClick={internalClickHandler}
onSubmit={internalSubmitHandler}
/>
Protecting multiple styling props:
const { className, style, theme, ...rest } = config;
<Card
{...rest}
className="card-base"
style={computedStyles}
theme={activeTheme}
/>
Summary
- Later props win: In react spread props, properties placed after the spread operator overwrite values from the spread.
- Order matters: Place protected props after the spread in JSX to guarantee they take precedence.
- Destructuring protects: Remove sensitive keys from the spread object using destructuring to prevent them from ever reaching the component.
- Internal consistency: React's
packages/react/src/jsx/ReactJSXElement.jsimplements standard JavaScript object merging rules (lines 335-344 for JSX, lines 80-108 for cloneElement).
Frequently Asked Questions
Can spread props override the key or ref special props in React?
No. According to the implementation in packages/react/src/jsx/ReactJSXElement.js (lines 335-344), React explicitly skips reserved keys including key, ref, __self, and __source when processing the config object. These special props must be assigned directly in JSX and cannot be injected via the spread operator.
What happens if I place a spread operator after an explicit prop?
The spread will overwrite the explicit prop. If you write <Button className="base" {...props} /> and props contains a className, the value from props wins. To protect your className, you must place it after the spread: <Button {...props} className="base" />.
Is there a performance difference between destructuring and placing props after the spread?
The runtime cost of destructuring is negligible compared to React's reconciliation process. Destructuring creates a new object without specific keys, while ordered JSX props rely on the same internal merging logic. Choose based on code clarity—use destructuring when you need to inspect or transform the removed values, and use ordering for simple overrides.
How does cloneElement differ from JSX spread behavior?
When using cloneElement(element, config), the config object always overwrites the original element's props because React iterates through the config after copying existing props (lines 80-84 and 102-108 in ReactJSXElement.js). This behavior mirrors placing the spread after explicit props in JSX, making cloneElement less forgiving for prop protection unless you manually merge objects beforehand.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →