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 performonFinish: 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
typeandduration - 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 identificationduration: 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.