Files
v10/.claude/skills/api/references/anti-patterns.md
T

7.1 KiB

Anti-Patterns

Common mistakes to avoid when designing or evaluating TypeScript library APIs.

API Design Anti-Patterns

Function Overloads

// Poor: TypeScript errors become "none of the 5 overloads match"
function create(config: Config): Store;
function create(initialState: State): Store;
function create(initialState: State, options: Options): Store;

// Good: Single config object
function create(config: { initialState?: State; options?: Options }): Store;

Why it fails: Autocomplete can't determine intended overload. Each optional parameter multiplies overload count.

Boolean Traps

// Poor: What do these booleans mean?
createPlayer(true, false, true);

// Good: Named options
createPlayer({ autoplay: true, muted: false, loop: true });

Multiple Competing APIs

// Poor: Confusing - which to use?
store.set({ volume: 0.5 });
store.setState({ volume: 0.5 });
store.update({ volume: 0.5 });
store.volume = 0.5;

// Good: One obvious way
store.setState({ volume: 0.5 });

Runtime Plugin Registration

// Poor: Loses type safety, implicit ordering
store.use(middleware1);
store.use(middleware2);

// Good: Composition at creation
create(middleware1(middleware2(fn)));

Implicit Magic Dependencies

// Poor: Where does MediaContext come from?
function useVolume() {
  const { volume } = useContext(MediaContext); // Not obvious
  return volume;
}

// Good: Explicit dependency
function useVolume(store: MediaStore) {
  return useSnapshot(store.state).volume;
}

Over-Abstraction

// Poor: AbstractFactoryManagerProvider
const factory = new PlayerFactoryManager();
const provider = factory.createProvider();
const player = provider.getInstance();

// Good: Direct API
const player = createPlayer();

TypeScript Anti-Patterns

Forcing Explicit Generics

// Poor: User must annotate
const store = createStore<MyState, MyActions>({ ... });

// Good: Infer from usage
const store = createStore({ ... });
type State = typeof store.state;

unknown in Public API

// Poor: Forces casting everywhere
onError: (error: unknown) => void

// Good: Typed errors
onError: (error: MediaError) => void

Deep Generic Nesting

// Poor: Inference fails, unreadable
type Store<T extends Record<K, V>, K extends string, V extends Serializable<V>>

// Good: Simpler constraints
type Store<T extends Record<string, unknown>>

Shotgun Parsing

// Poor: Validation scattered everywhere
function processUser(data: unknown) {
  if (!data.name) throw new Error('Missing name');
  // ... 100 lines later ...
  if (!data.email) throw new Error('Missing email');
}

// Good: Parse at boundaries, trust types internally
const user = userSchema.parse(request.body);
processUser(user); // Type guarantees fields exist

State Management Anti-Patterns

Mandatory Providers

// Poor: Boilerplate for every usage
<StoreProvider store={store}>
  <App />
</StoreProvider>

// Good: Works without wrapper
const useStore = create((set) => ({ ... }));
// Optional provider for overrides/testing

No Selector Support

// Poor: Subscribes to everything, excessive re-renders
const state = useStore();

// Good: Fine-grained subscriptions
const volume = useStore((s) => s.volume);

Sync-Only State

// Poor: Async requires workarounds
store.setState({ loading: true });
const data = await fetch(...);
store.setState({ loading: false, data });

// Good: Built-in async support
const dataAtom = atom(async () => fetch(...));

Per-Module Middleware

// Poor: Unexpected interactions
const volumeFeature = createFeature({
  middleware: [logger], // Don't do this
});

// Good: Middleware at store level
create(
  logger((...a) => ({
    ...volumeFeature(...a),
    ...playbackFeature(...a),
  }))
);

Error Handling Anti-Patterns

Generic Errors

// Poor: No context, not actionable
throw new Error('Invalid state');

// Good: Typed, contextual, actionable
throw new MediaError('INVALID_STATE', {
  current: state,
  expected: ['idle', 'ready'],
  hint: 'Call reset() before attempting this operation',
});

Swallowing Errors

// Poor: Silent failure
try {
  await load();
} catch {
  // nothing
}

// Good: Explicit handling
try {
  await load();
} catch (error) {
  onError?.(error);
  setState({ error });
}

Inconsistent Async Errors

// Poor: Different patterns
methodA().catch(handler);          // Promise rejection
methodB((err) => { ... });         // Callback
methodC();  // throws? returns error? who knows

// Good: Consistent pattern
const result = await methodA();  // Returns Result<T, E>
if (!result.ok) handle(result.error);

Packaging Anti-Patterns

Deep Subpaths

// Poor: Unwieldy imports

// Good: Shallow subpaths
import { useStore } from '@lib/react';
import { useStore } from '@lib/react/hooks/store/useStore';

Barrel Export Everything

// Poor: Tree-shaking issues, TS perf hit
export * from './components';
export * from './hooks';
export * from './utils';
// ... 50 more

// Good: Explicit exports
export { Button } from './Button';
export { useStore } from './useStore';

Bundled Framework Dependencies

// Poor: Bundles React, version conflicts
"dependencies": {
  "react": "^18.0.0"
}

// Good: Peer dependency
"peerDependencies": {
  "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
}

Documentation Anti-Patterns

Examples Without Types

// Poor: JavaScript only
const store = createStore({
  count: 0,
  increment() { ... }
});

// Good: TypeScript with inference visible
const store = createStore({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
});
// type State = { count: number; increment: () => void }

Missing Error Scenarios

// Poor: Only happy path
const data = await query.fetch();

// Good: Shows error handling
const data = await query.fetch();
if (query.error) {
  if (query.error instanceof NetworkError) { ... }
}

Quick Reference

Anti-Pattern Why It Fails
Function overloads Poor errors, autocomplete confusion
Runtime plugin registration Loses type safety, implicit ordering
Positional parameters (3+) Order confusion, breaking changes
Implicit contracts Silent breakage when requirements unmet
Per-module middleware Unexpected interactions
Shotgun parsing Validation scattered, not at boundaries
Boolean traps fn(true, false) — what do these mean?
Multiple competing APIs Confusing — which method to use?
Deep generic nesting Inference fails, unreadable errors
Barrel exports Tree-shaking issues, TS performance

See Also