Skip to content
Sarj Code Standards

no-excess-jsx-props

Report statically known fields spread outside a resolved component props contract.

Why

A spread can forward unrelated data even when structural typing accepts the larger object. Changing its annotation does not remove runtime fields.

Fix

Forward the declared fields explicitly or review the receiving contract. A projection changes object identity and allocations; this rule never constructs one automatically.

Examples

Before — flagged A proven primitive alias preserves the finding
jsx.d.ts
declare module "react/jsx-runtime" {
export namespace JSX {
interface Element {}
interface IntrinsicElements {
span: { children?: unknown };
}
}
}
src/avatar.tsx
export {};
const name = "Ada";
const Avatar = ({ displayName }: { displayName: string }) => (
<span>{`${displayName}`}</span>
);
const element = <Avatar {...{ displayName: name, email: "unused" }} />;
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}
After — preferred An asserted alias can still observe forwarded data
jsx.d.ts
declare module "react/jsx-runtime" {
export namespace JSX {
interface Element {}
interface IntrinsicElements {
span: { children?: unknown };
}
}
}
src/avatar.tsx
export {};
let element: any;
const opaque = {
toString() {
return element.props.email;
},
};
const name = opaque as unknown as string;
const Avatar = ({ displayName }: { displayName: string }) => (
<span>{`${displayName}`}</span>
);
element = <Avatar {...{ displayName: name, email: "needed" }} />;
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}
Before — flagged A private element exposes only its consumed scalar
jsx.d.ts
declare module "react/jsx-runtime" {
export namespace JSX {
interface Element {}
interface IntrinsicElements {
span: { children?: unknown };
}
}
}
src/avatar.tsx
export {};
const Avatar = ({ displayName }: { displayName: string }) => <span>{displayName}</span>;
const element = <Avatar {...{ displayName: "Ada", email: "unused" }} />;
const summary = {
displayName: (element as { props: { displayName: string } }).props.displayName,
};
export { summary };
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}
After — preferred An opaque consumer can observe or mutate element props
jsx.d.ts
declare module "react/jsx-runtime" {
export namespace JSX {
interface Element {}
interface IntrinsicElements {
span: { children?: unknown };
}
}
}
src/avatar.tsx
export {};
declare function inspect(value: unknown): void;
const Avatar = ({ displayName }: { displayName: string }) => <span>{displayName}</span>;
const element = <Avatar {...{ displayName: "Ada", email: "needed" }} />;
inspect(element);
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}
Before — flagged An unrelated data object's metadata preserves the finding
jsx.d.ts
declare module "react/jsx-runtime" {
export namespace JSX {
interface Element {}
interface IntrinsicElements {
span: { children?: unknown };
}
}
}
src/avatar.tsx
export {};
const settings = { defaultProps: { email: "fallback" } };
settings.defaultProps = { email: "updated" };
const Avatar = ({ displayName }: { displayName: string }) => <span>{displayName}</span>;
const element = <Avatar {...{ displayName: "Ada", email: "unused" }} />;
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}
After — preferred Runtime metadata can observe an omitted field
jsx.d.ts
declare module "react/jsx-runtime" {
export namespace JSX {
interface Element {}
interface IntrinsicElements {
span: { children?: unknown };
}
}
}
src/avatar.tsx
export {};
const target = { ref: 0 };
const Avatar = ({ displayName }: { displayName: string }) => <span>{displayName}</span>;
(Avatar as unknown as { defaultProps: { email: string } }).defaultProps = {
get email() {
target.ref = 1;
return "fallback";
},
};
const element = <Avatar {...{ displayName: "Ada", email: "needed" }} />;
const observation = target.ref;
export { observation };
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}
Before — flagged An unrelated field is forwarded at runtime
jsx.d.ts
declare module "react/jsx-runtime" {
export namespace JSX {
interface Element {}
interface IntrinsicElements {
span: { children?: unknown };
}
}
}
src/avatar.tsx
export {};
const Avatar = ({ displayName }: { displayName: string }) => <span>{displayName}</span>;
const avatar = <Avatar {...{ displayName: "Ada", email: "ada@example.test" }} />;
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}
After — preferred Forward only the declared field
jsx.d.ts
declare module "react/jsx-runtime" {
export namespace JSX {
interface Element {}
interface IntrinsicElements {
span: { children?: unknown };
}
}
}
src/avatar.tsx
export {};
const Avatar = ({ displayName }: { displayName: string }) => <span>{displayName}</span>;
const avatar = <Avatar displayName="Ada" />;
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}
Before — flagged An unrelated own-property write preserves the finding
jsx.d.ts
declare module "react/jsx-runtime" {
export namespace JSX {
interface Element {}
interface IntrinsicElements {
span: { children?: unknown };
}
}
}
src/avatar.tsx
export {};
const target = { ref: null };
target.ref = null;
const Avatar = ({ displayName }: { displayName: string }) => <span>{displayName}</span>;
const element = <Avatar {...{ displayName: "Ada", email: "unused" }} />;
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}
After — preferred An inherited runtime hook can observe forwarded data
jsx.d.ts
declare module "react/jsx-runtime" {
export namespace JSX {
interface Element {}
interface IntrinsicElements {
span: { children?: unknown };
}
}
}
src/avatar.tsx
export {};
let element: any;
const Avatar = ({ displayName }: { displayName: string }) => <span>{displayName}</span>;
element = <Avatar {...{ displayName: "Ada", email: "needed" }} />;
Object.defineProperty(Object.prototype, "ref", {
configurable: true,
get() {
Object.defineProperty(this, "children", {
configurable: true,
value: element.props.email,
});
return null;
},
});
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}

References