Skip to main content

Types & Interfaces

The @breathing-exercise module provides comprehensive TypeScript support with well-defined types and interfaces that ensure type safety, improve developer experience, and enable better IDE support. This documentation covers all the available types and their usage patterns.

Overview

The breathing exercise module's type system is designed around the core concepts of breathing patterns, exercise configuration, and component props. All types are exported from the main module and can be imported individually or as a group.

Core Types

BreathingExercise

The fundamental type defining a complete breathing exercise configuration.

export type BreathingExercise = {
methods: BreathingPattern;
repeat: number;
onFinish?: () => void;
};

Properties:

  • methods: The breathing pattern sequence (inhale-hold-exhale)
  • repeat: Number of complete cycles to perform
  • onFinish: Optional callback when exercise completes

Usage:

const exerciseConfig: BreathingExercise = {
methods: [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
],
repeat: 5,
onFinish: () => console.log("Exercise completed!"),
};

BreathingPattern

Defines the structure for a complete breathing cycle with three phases.

type BreathingPattern = [
{ type: "inhale"; duration: number },
{ type: "hold"; duration: number },
{ type: "exhale"; duration: number },
];

Characteristics:

  • Fixed tuple of exactly 3 elements
  • Each element has a specific type and duration
  • Order matters: inhale → hold → exhale
  • Durations are in seconds

Examples:

// Box breathing (4-4-4)
const boxBreathing: BreathingPattern = [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
];

// 4-7-8 relaxation technique
const relaxingBreath: BreathingPattern = [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 7 },
{ type: "exhale", duration: 8 },
];

// Quick breathing
const quickBreath: BreathingPattern = [
{ type: "inhale", duration: 2 },
{ type: "hold", duration: 1 },
{ type: "exhale", duration: 3 },
];

BreathingMethod

Individual breathing phase configuration within a pattern.

type BreathingMethod = {
type: "inhale" | "hold" | "exhale";
duration: number;
};

Type Constraints:

  • type: Literal union type for phase identification
  • duration: Positive number representing seconds

Usage:

const inhalePhase: BreathingMethod = {
type: "inhale",
duration: 4,
};

// Type checking prevents invalid types
const invalidPhase: BreathingMethod = {
type: "breathe", // ❌ Type error: not assignable
duration: 4,
};

Component Props Types

BreathingExerciseProps

Props for the main BreathingExercise component, extending React Native's ViewProps.

export type BreathingExerciseProps = ViewProps & BreathingExercise;

Extended Properties:

interface FullBreathingExerciseProps extends ViewProps {
// From BreathingExercise
methods: BreathingPattern;
repeat: number;
onFinish?: () => void;

// From ViewProps
style?: ViewStyle;
onLayout?: (event: LayoutChangeEvent) => void;
// ... all other ViewProps
}

Usage:

const MyBreathingExercise: React.FC<BreathingExerciseProps> = ({
methods,
repeat,
onFinish,
style,
...viewProps
}) => {
return (
<BreathingExercise
methods={methods}
repeat={repeat}
onFinish={onFinish}
style={[defaultStyles.container, style]}
{...viewProps}
>
{/* Components */}
</BreathingExercise>
);
};

BreathingExerciseAnimationProps

Props for the animation component, extending LottieViewProps but excluding the source prop.

export type BreathingExerciseAnimationProps = Omit<LottieViewProps, "source">;

Rationale: The source prop is omitted because the breathing animation uses a hardcoded Lottie file (breathing.json), preventing conflicts with external source specifications.

Available Props:

interface AnimationProps {
style?: StyleProp<ViewStyle>;
resizeMode?: "contain" | "cover" | "center";
renderMode?: "automatic" | "hardware" | "software";
speed?: number;
loop?: boolean;
autoPlay?: boolean;
// ... other LottieViewProps except source
}

Usage:

<BreathingExercise.Animation
style={styles.animation}
resizeMode="contain"
speed={1.0}
loop={false}
/>

Context Types

BreathingExerciseContextType

Complete type definition for the breathing exercise context value.

export type BreathingExerciseContextType = BreathingExercise & {
progress: SharedValue<number>;
roundData: SharedValue<{
method: BreathingExercise["methods"][number];
animationProgress: number;
remainingTime: number;
}>;
remainingTime: number;
methodType: BreathingExercise["methods"][number]["type"];
};

Breakdown:

interface DetailedContextType {
// Static configuration (from BreathingExercise)
methods: BreathingPattern;
repeat: number;
onFinish?: () => void;

// Animated values (React Native Reanimated)
progress: SharedValue<number>; // 0-1 overall progress
roundData: SharedValue<{
method: BreathingMethod; // Current phase object
animationProgress: number; // 0-1 animation progress
remainingTime: number; // Seconds remaining in phase
}>;

// React state values
remainingTime: number; // Current phase remaining time
methodType: "inhale" | "hold" | "exhale"; // Current phase type
}

Usage in Custom Hooks:

const useCustomBreathingLogic = (): BreathingExerciseContextType => {
const context = useBreathingExercise();

// TypeScript ensures all required properties are available
const {
progress,
roundData,
remainingTime,
methodType,
methods,
repeat,
onFinish,
} = context;

return context;
};

Utility Types

Type Extractors

Extract specific types from the main types for utility functions.

// Extract method type
type MethodType = BreathingExercise["methods"][number]["type"];
// Result: "inhale" | "hold" | "exhale"

// Extract method object
type MethodObject = BreathingExercise["methods"][number];
// Result: { type: "inhale" | "hold" | "exhale"; duration: number }

// Extract duration type
type Duration = BreathingExercise["methods"][number]["duration"];
// Result: number

Generic Helper Types

// Optional configuration type
type PartialBreathingExercise = Partial<BreathingExercise>;

// Required configuration type (removes optional onFinish)
type RequiredBreathingConfig = Required<
Pick<BreathingExercise, "methods" | "repeat">
>;

// Method type guards
type IsInhale<T> = T extends { type: "inhale" } ? true : false;
type IsHold<T> = T extends { type: "hold" } ? true : false;
type IsExhale<T> = T extends { type: "exhale" } ? true : false;

Type Guards

Runtime Type Checking

// Method type guard
export const isValidMethodType = (
type: string,
): type is BreathingMethod["type"] => {
return ["inhale", "hold", "exhale"].includes(type);
};

// Breathing pattern validation
export const isValidBreathingPattern = (
pattern: unknown,
): pattern is BreathingPattern => {
return (
Array.isArray(pattern) &&
pattern.length === 3 &&
pattern.every((method, index) => {
const expectedTypes = ["inhale", "hold", "exhale"] as const;
return (
typeof method === "object" &&
method !== null &&
"type" in method &&
"duration" in method &&
method.type === expectedTypes[index] &&
typeof method.duration === "number" &&
method.duration > 0
);
})
);
};

// Exercise configuration validation
export const isValidBreathingExercise = (
config: unknown,
): config is BreathingExercise => {
return (
typeof config === "object" &&
config !== null &&
"methods" in config &&
"repeat" in config &&
isValidBreathingPattern((config as any).methods) &&
typeof (config as any).repeat === "number" &&
(config as any).repeat > 0 &&
(!(config as any).onFinish ||
typeof (config as any).onFinish === "function")
);
};

Usage:

const validateUserInput = (userConfig: unknown) => {
if (isValidBreathingExercise(userConfig)) {
// TypeScript now knows userConfig is BreathingExercise
return userConfig;
}

throw new Error("Invalid breathing exercise configuration");
};

Advanced Type Patterns

Conditional Types

// Method-specific configuration
type MethodSpecificConfig<T extends MethodType> = T extends "inhale"
? { guidance: "Breathe in slowly"; icon: "arrow-up" }
: T extends "hold"
? { guidance: "Hold your breath"; icon: "pause" }
: T extends "exhale"
? { guidance: "Breathe out completely"; icon: "arrow-down" }
: never;

// Usage
type InhaleConfig = MethodSpecificConfig<"inhale">;
// Result: { guidance: 'Breathe in slowly'; icon: 'arrow-up' }

// Phase-specific props
type PhaseSpecificProps<T extends MethodType> = {
type: T;
config: MethodSpecificConfig<T>;
};

Mapped Types

// Create configuration for all method types
type AllMethodConfigs = {
[K in MethodType]: MethodSpecificConfig<K>;
};

// Result:
// {
// inhale: { guidance: 'Breathe in slowly'; icon: 'arrow-up' };
// hold: { guidance: 'Hold your breath'; icon: 'pause' };
// exhale: { guidance: 'Breathe out completely'; icon: 'arrow-down' };
// }

// Duration mapping
type MethodDurations = {
[K in MethodType]: number;
};

Template Literal Types

// Generate translation keys
type TranslationKey<T extends MethodType> = `breathing-exercise.method.${T}`;

// Usage
type InhaleKey = TranslationKey<"inhale">;
// Result: "breathing-exercise.method.inhale"

// Generate all translation keys
type AllTranslationKeys = {
[K in MethodType]: TranslationKey<K>;
};

Component Ref Types

React Ref Types

// Main component ref
type BreathingExerciseRef = React.ComponentRef<typeof BreathingExercise>;

// Animation component ref
type AnimationRef = React.ComponentRef<typeof LottieView>;

// Text component refs for sub-components
type MethodNameRef = React.ComponentRef<typeof Text>;
type RemainingTimeRef = React.ComponentRef<typeof Text>;

Usage:

const BreathingWithRefs = () => {
const exerciseRef = React.useRef<BreathingExerciseRef>(null);
const animationRef = React.useRef<AnimationRef>(null);

const focusExercise = () => {
exerciseRef.current?.focus();
};

return (
<BreathingExercise ref={exerciseRef}>
<BreathingExercise.Animation ref={animationRef} />
</BreathingExercise>
);
};

Event Handler Types

Callback Types

// Completion callback
type CompletionCallback = () => void;

// Phase change callback
type PhaseChangeCallback = (
newPhase: MethodType,
previousPhase: MethodType | null,
phaseData: {
duration: number;
remainingTime: number;
progress: number;
},
) => void;

// Progress callback
type ProgressCallback = (progress: {
overall: number;
phase: number;
remaining: number;
}) => void;

Extended Exercise Type:

type ExtendedBreathingExercise = BreathingExercise & {
onPhaseChange?: PhaseChangeCallback;
onProgress?: ProgressCallback;
onStart?: () => void;
onPause?: () => void;
onResume?: () => void;
};

Type Assertions and Narrowing

Safe Type Assertions

// Safe casting with validation
const safeAsBreathingExercise = (value: unknown): BreathingExercise => {
if (isValidBreathingExercise(value)) {
return value;
}

throw new Error("Invalid breathing exercise configuration");
};

// Partial validation and completion
const completeBreathingConfig = (
partial: Partial<BreathingExercise>,
): BreathingExercise => {
const defaults: BreathingExercise = {
methods: [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
],
repeat: 5,
};

return { ...defaults, ...partial };
};

Discriminated Unions

// Phase-specific data
type PhaseData =
| { type: "inhale"; guidance: string; intensity: "gentle" | "deep" }
| { type: "hold"; guidance: string; comfort: "easy" | "challenging" }
| { type: "exhale"; guidance: string; completeness: "partial" | "full" };

// Type narrowing function
const getPhaseSpecificData = (method: MethodType): PhaseData => {
switch (method) {
case "inhale":
return {
type: "inhale",
guidance: "Breathe in slowly and deeply",
intensity: "gentle",
};
case "hold":
return {
type: "hold",
guidance: "Hold your breath comfortably",
comfort: "easy",
};
case "exhale":
return {
type: "exhale",
guidance: "Breathe out completely",
completeness: "full",
};
default:
// TypeScript ensures this is never reached
const _exhaustive: never = method;
throw new Error(`Unknown method type: ${_exhaustive}`);
}
};

Testing Types

Mock Types

// Mock context type for testing
type MockBreathingExerciseContext = {
[K in keyof BreathingExerciseContextType]: K extends "progress" | "roundData"
? { value: any } // Simplified SharedValue mock
: BreathingExerciseContextType[K];
};

// Test utilities
type TestBreathingExercise = {
config: BreathingExercise;
expectedDuration: number;
expectedPhases: MethodType[];
};

Usage in Tests:

const createMockContext = (
overrides: Partial<MockBreathingExerciseContext> = {},
): MockBreathingExerciseContext => ({
methods: [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
],
repeat: 1,
progress: { value: 0 },
roundData: {
value: {
method: { type: "inhale", duration: 4 },
animationProgress: 0,
remainingTime: 4,
},
},
remainingTime: 4,
methodType: "inhale",
...overrides,
});

Best Practices

1. Type Safety

// ✅ Good: Use type guards for runtime validation
const processUserConfig = (config: unknown) => {
if (isValidBreathingExercise(config)) {
return config; // TypeScript knows this is BreathingExercise
}
throw new Error("Invalid configuration");
};

// ❌ Bad: Unsafe type assertion
const processUserConfigBad = (config: unknown) => {
return config as BreathingExercise; // No runtime validation
};

2. Generic Constraints

// ✅ Good: Constrained generics
type MethodSpecificComponent<T extends MethodType> = {
type: T;
render: (props: MethodSpecificConfig<T>) => React.ReactNode;
};

// ❌ Bad: Unconstrained generics
type BadComponent<T> = {
type: T; // T could be anything
render: (props: any) => React.ReactNode;
};

3. Utility Type Usage

// ✅ Good: Use utility types for transformations
type PartialExerciseConfig = Partial<BreathingExercise>;
type RequiredMethods = Required<Pick<BreathingExercise, "methods">>;

// ❌ Bad: Manual type definitions
type PartialExerciseConfigBad = {
methods?: BreathingPattern;
repeat?: number;
onFinish?: () => void;
};

Migration Guide

From Untyped to Typed

// Before: Untyped
const exercise = {
methods: [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
],
repeat: 5,
};

// After: Properly typed
const exercise: BreathingExercise = {
methods: [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
],
repeat: 5,
};

Adding Type Guards

// Before: Unsafe
const processConfig = (config: any) => {
return <BreathingExercise {...config} />;
};

// After: Type-safe
const processConfig = (config: unknown) => {
if (isValidBreathingExercise(config)) {
return <BreathingExercise {...config} />;
}
return <ErrorComponent message="Invalid configuration" />;
};

The comprehensive type system in the @breathing-exercise module ensures type safety, improves developer experience, and enables better tooling support while maintaining flexibility for various use cases.