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

> Discover how narrow-react-prop-types derives real types from live code paths by analyzing production call sites to tighten React prop definitions and remove unnecessary fallback logic.

- Repository: [HumanLayer/skills](https://github.com/humanlayer/skills)
- Tags: deep-dive
- Published: 2026-09-07

---

**`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](https://github.com/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](https://github.com/humanlayer/skills/blob/main/plugins/narrow-react-prop-types/skills/narrow-react-prop-types/SKILL.md#l26), 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"](https://github.com/humanlayer/skills/blob/main/plugins/narrow-react-prop-types/skills/narrow-react-prop-types/SKILL.md#l38).

## 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**:

```typescript
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

```typescript
// 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"](https://github.com/humanlayer/skills/blob/main/plugins/narrow-react-prop-types/skills/narrow-react-prop-types/SKILL.md#l69).

## 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:

```typescript
// 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"](https://github.com/humanlayer/skills/blob/main/plugins/narrow-react-prop-types/skills/narrow-react-prop-types/SKILL.md#l87).

## Removing Fallback Logic

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

```typescript
// 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"](https://github.com/humanlayer/skills/blob/main/plugins/narrow-react-prop-types/skills/narrow-react-prop-types/SKILL.md#l103).

## 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"](https://github.com/humanlayer/skills/blob/main/plugins/narrow-react-prop-types/skills/narrow-react-prop-types/SKILL.md#l129).

## Validating the Change

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

```bash
bun run typecheck --filter <package>

```

The skill's CI agent, configured in [agent-narrow-component-props.yml](https://github.com/humanlayer/skills/blob/main/plugins/narrow-react-prop-types/skills/narrow-react-prop-types/references/agent-narrow-component-props.yml), formats findings using [response-template.md](https://github.com/humanlayer/skills/blob/main/plugins/narrow-react-prop-types/skills/narrow-react-prop-types/references/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"](https://github.com/humanlayer/skills/blob/main/plugins/narrow-react-prop-types/skills/narrow-react-prop-types/SKILL.md#l140).

## Key Files in the Repository

| File | Role |
|------|------|
| [`SKILL.md`](https://github.com/humanlayer/skills/blob/main/SKILL.md) | Primary documentation of workflow, requirements, and type-derivation techniques |
| [`references/agent-narrow-component-props.yml`](https://github.com/humanlayer/skills/blob/main/references/agent-narrow-component-props.yml) | CI-agent workflow for running the skill and collecting live usage data |
| [`references/response-template.md`](https://github.com/humanlayer/skills/blob/main/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`](https://github.com/humanlayer/skills/blob/main/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`](https://github.com/humanlayer/skills/blob/main/response-template.md).