How `narrow-react-prop-types` Derives Real Types from Live Code Paths

narrow-react-prop-types analyzes production call sites to tighten React prop definitions, eliminating optional fields and fallback logic that exists only for tests or Storybook demos.

narrow-react-prop-types is a skill from the humanlayer/skills repository that enforces a source-of-truth model for TypeScript React components: the live application code dictates the prop contract, not auxiliary files. This article explains the complete architectural process, from identifying suspect components to validating the final changes.

Identifying Suspect Components

The skill begins by locating components whose prop interfaces show signs of deliberate widening for non-production scenarios. Common indicators include:

  • Optional fields that are always present in actual usage
  • Optional callbacks like onSelect?.()
  • Fallback expressions such as items ?? []

These patterns suggest the type was relaxed to accommodate Storybook stories, tests, or mock data rather than reflecting real runtime requirements. According to the skill's SKILL.md, this step requires human judgment to flag components worth analyzing.

Finding Every Live Usage

Once a target is selected, the skill performs a repository-wide search for all imports of the component and its exported prop types. Each import site is classified into one of two categories:

  • Live code paths — application routes, wired components, production package exports
  • Support code — Storybook stories, tests, fixtures

Only live code paths are permitted to influence the final prop shape. Support code must adapt to the stricter contract, not the reverse.

This classification is documented in SKILL.md – "Find every live usage".

Deriving the Real Types

For each prop, the skill applies a three-way decision:

  • Required — every live call site provides the prop
  • Optional — at least one live call site omits it, and the omission represents meaningful runtime state
  • Removed — no live call site uses the prop at all

Nullability vs. Optionality

The skill distinguishes these carefully. A prop always passed but potentially null remains required with a nullable type:

focusedItem: FocusedItem | null

This preserves type safety without incorrectly marking the prop as optional.

Leveraging TypeScript Utilities

Rather than manually reconstructing types, narrow-react-prop-types encourages derivation from existing live-code values using standard TypeScript utilities:

Utility Purpose
Parameters<typeof fn>[0] Extract a function's first argument type
ReturnType<typeof fn> Capture a function's return type
Extract<Union, Shape> Narrow a union to the variant actually used
React.Dispatch<React.SetStateAction<T>> Type React state setters precisely

These utilities ensure prop definitions stay synchronized with underlying APIs and prevent accidental widening.

Example: Deriving Props from Live Functions

// Live utility function
export function fetchUser(id: string): Promise<User> { /* ... */ }

// Component prop derived from the live API
type FetchUserProps = {
  /** The `id` argument is required because every live call supplies it */
  id: Parameters<typeof fetchUser>[0];
  /** The component receives the resolved user, never `undefined` */
  onSuccess: (user: Awaited<ReturnType<typeof fetchUser>>) => void;
};

This pattern is detailed in SKILL.md – "Derive and extract types where possible".

Tightening Internal Child Props

After narrowing the exported prop type, the skill propagates strictness to child components that received the same loosened props. Optional calls become required:

// Before narrowing
interface MenuProps {
  onRename?: (id: string, name: string) => void;
}
// ...
onRename?.(id, name); // optional call

// After narrowing
interface MenuProps {
  onRename: (id: string, name: string) => void;
}
// ...
onRename(id, name); // required call

This propagation ensures internal consistency across the component tree, as described in SKILL.md – "Tighten internal child props too".

Removing Fallback Logic

Once a prop is proven required, defensive code patterns are eliminated. Unreachable branches are removed:

// Before
const expandedSet = new Set(expandedIds ?? defaultExpandedIds ?? []);

// After
const expandedSet = new Set(expandedIds);

This simplification reduces runtime branching and improves maintainability. See SKILL.md – "Remove fallback logic for unsupported states".

Updating Dependent Artifacts

Stories, tests, and fixtures that relied on the previous loose contract must be revised to satisfy the new strict contract. The skill does not re-introduce optionality to simplify test setup. Instead, realistic helpers or fixtures are added to support code.

This principle is enforced in SKILL.md – "Let tests and stories adapt to live code".

Validating the Change

The final step runs the package's type-check to ensure all consuming live applications still compile:

bun run typecheck --filter <package>

The skill's CI agent, configured in agent-narrow-component-props.yml, formats findings using response-template.md. The resulting PR body includes:

  • Changed components
  • Rationales for each change
  • Live call sites analyzed
  • Validation results

This structured output is documented in SKILL.md – "Validate the change".

Key Files in the Repository

File Role
SKILL.md Primary documentation of workflow, requirements, and type-derivation techniques
references/agent-narrow-component-props.yml CI-agent workflow for running the skill and collecting live usage data
references/response-template.md Template for consistent PR body formatting

Summary

  • narrow-react-prop-types treats live code paths as the sole source of truth for prop contracts
  • TypeScript utilities like Parameters and ReturnType keep types derived from actual values, not manually maintained
  • The skill distinguishes nullability from optionality to preserve accurate runtime semantics
  • Support code adapts to live code, never the reverse
  • Fallback logic and optional calls are eliminated when proven unnecessary
  • Validation ensures type safety across all consuming applications

Frequently Asked Questions

What makes a React component a candidate for narrow-react-prop-types?

Components with prop interfaces containing many optional fields, optional callbacks (onSelect?.()), or fallback expressions (items ?? []) are strong candidates. These patterns indicate deliberate widening for non-production scenarios like Storybook or tests, suggesting the actual runtime contract is stricter.

How does the skill handle props that are always passed but can be null?

The skill marks such props as required with a nullable type (e.g., focusedItem: FocusedItem | null). This preserves the distinction between "must be provided" and "can be absent," avoiding the loss of type information that would occur if the prop were made optional.

What happens to tests and stories that break after narrowing?

Tests and stories are updated to satisfy the new strict contract, typically by adding realistic fixtures or helpers. The skill explicitly does not re-introduce optionality or widen types to accommodate support code. This enforces the principle that live code determines the contract.

Can narrow-react-prop-types be run automatically in CI?

Yes. The skill includes agent-narrow-component-props.yml, which configures a CI agent to run the analysis, collect live usage data, and format findings into a PR body using the standardized response-template.md.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →