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-typestreats live code paths as the sole source of truth for prop contracts- TypeScript utilities like
ParametersandReturnTypekeep 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →