Skip to main content

Breathing Exercise

The @breathing-exercise module provides a comprehensive solution for implementing guided breathing exercises in React Native applications. It combines smooth animations, real-time progress tracking, and flexible configuration to create an engaging and therapeutic breathing experience.

Overview

The Breathing Exercise module offers a complete compound component system for creating guided breathing sessions. It features synchronized Lottie animations, real-time progress tracking, customizable breathing patterns, and a modular architecture that allows for flexible UI composition.

Key Features

  • 🎯 Guided Breathing Patterns: Configurable inhale-hold-exhale sequences
  • 🎨 Smooth Animations: Lottie-based animations synchronized with breathing phases
  • 📊 Real-time Progress: Live progress tracking and remaining time display
  • 🔄 Customizable Cycles: Configurable repetition counts and timing
  • 🧩 Compound Components: Flexible UI composition with sub-components
  • ♿ Accessibility: Full screen reader and keyboard navigation support
  • 🌐 Internationalization: Multi-language support for method names
  • ⚡ Performance: Optimized with React Native Reanimated
  • 🎭 Theming: Integration with React Native Paper theming

Key Components

Primary Components

  • BreathingExercise: Main compound component container
  • BreathingExerciseProvider: Context provider for state management
  • useBreathingExercise: Hook for accessing exercise state

Sub-Components

  • ProgressBar: Visual progress indicator
  • AnimationWrapper: Container for breathing animation
  • Animation: Lottie animation component
  • MethodTypeIcon: Icon for current breathing phase
  • Info: Information display container
  • MethodName: Current phase name display
  • RemainingTime: Time countdown display

Icon Components

  • InhaleIcon: Inhale phase icon
  • HoldIcon: Hold phase icon
  • ExhaleIcon: Exhale phase icon

Quick Start

Basic Usage

import { BreathingExercise } from "@breathing-exercise";

export const BasicBreathingSession = () => {
const handleFinish = () => {
console.log("Breathing exercise completed!");
};

return (
<BreathingExercise
methods={[
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
]}
repeat={5}
onFinish={handleFinish}
>
<BreathingExercise.ProgressBar />
<BreathingExercise.AnimationWrapper>
<BreathingExercise.Animation />
</BreathingExercise.AnimationWrapper>
<BreathingExercise.Info>
<BreathingExercise.MethodTypeIcon />
<BreathingExercise.MethodName variant="headlineMedium" />
<BreathingExercise.RemainingTime />
</BreathingExercise.Info>
</BreathingExercise>
);
};

Advanced Configuration

import { BreathingExercise } from "@breathing-exercise";
import { View, StyleSheet } from "react-native";

export const Advanced478BreathingSession = () => {
const handleFinish = () => {
// Navigate to completion screen or show success message
};

return (
<View style={styles.container}>
<BreathingExercise
methods={[
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 7 },
{ type: "exhale", duration: 8 },
]}
repeat={10}
onFinish={handleFinish}
style={styles.exercise}
>
<BreathingExercise.ProgressBar style={styles.progressBar} />

<BreathingExercise.AnimationWrapper style={styles.animationContainer}>
<BreathingExercise.Animation
style={styles.animation}
resizeMode="contain"
/>
</BreathingExercise.AnimationWrapper>

<BreathingExercise.Info style={styles.info}>
<View style={styles.iconContainer}>
<BreathingExercise.MethodTypeIcon size={48} />
</View>

<BreathingExercise.MethodName
variant="headlineLarge"
style={styles.methodName}
/>

<BreathingExercise.RemainingTime
variant="bodyLarge"
style={styles.remainingTime}
/>
</BreathingExercise.Info>
</BreathingExercise>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
exercise: {
flex: 1,
},
progressBar: {
marginBottom: 40,
},
animationContainer: {
flex: 1,
justifyContent: "center",
},
animation: {
width: "100%",
height: "100%",
},
info: {
alignItems: "center",
gap: 16,
},
iconContainer: {
marginBottom: 8,
},
methodName: {
textAlign: "center",
},
remainingTime: {
opacity: 0.7,
},
});

Breathing Patterns

Common Patterns

4-4-4 (Box Breathing)

const boxBreathing = [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
];

4-7-8 (Relaxing Breath)

const relaxingBreath = [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 7 },
{ type: "exhale", duration: 8 },
];

4-2-4-2 (Coherent Breathing)

const coherentBreathing = [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 2 },
{ type: "exhale", duration: 4 },
];

Architecture

Component Hierarchy

BreathingExercise (Provider + Container)
├── BreathingExercise.ProgressBar
├── BreathingExercise.AnimationWrapper
│ └── BreathingExercise.Animation
├── BreathingExercise.Info
│ ├── BreathingExercise.MethodTypeIcon
│ ├── BreathingExercise.MethodName
│ └── BreathingExercise.RemainingTime

State Management

The module uses React Context with React Native Reanimated for state management:

  • Context Provider: BreathingExerciseProvider manages the exercise state
  • Shared Values: Progress and timing calculations using useSharedValue
  • Derived Values: Real-time calculations for animation and display
  • State Synchronization: UI updates synchronized with animation progress

Animation System

  • Lottie Integration: Uses react-native-lottie for smooth breathing animations
  • Progress Synchronization: Animation progress tied to breathing phase progress
  • Performance Optimized: Runs on UI thread using React Native Reanimated

Styling

Theme Integration

The module integrates with React Native Paper's theming system:

import { useAppTheme } from "@hooks/use-app-theme";

const MyBreathingSession = () => {
const theme = useAppTheme();

return (
<BreathingExercise
// ... exercise props
style={{ backgroundColor: theme.colors.surface }}
>
<BreathingExercise.MethodName style={{ color: theme.colors.primary }} />
{/* ... other components */}
</BreathingExercise>
);
};

Custom Styling

All components accept standard React Native style props:

<BreathingExercise style={customContainerStyle}>
<BreathingExercise.Animation style={customAnimationStyle} />
<BreathingExercise.MethodName style={customTextStyle} />
</BreathingExercise>

Accessibility

Screen Reader Support

  • Semantic Labels: All components have appropriate accessibility labels
  • Live Regions: Progress and time updates announced to screen readers
  • Content Description: Current breathing phase announced

Implementation

<BreathingExercise
accessible={true}
accessibilityRole="timer"
accessibilityLabel="Breathing exercise in progress"
>
<BreathingExercise.MethodName
accessibilityLiveRegion="polite"
accessibilityRole="text"
/>
<BreathingExercise.RemainingTime
accessibilityLiveRegion="polite"
accessibilityRole="timer"
/>
</BreathingExercise>

Performance

Optimization Features

  • UI Thread Animation: Critical animations run on UI thread
  • Memoized Calculations: Expensive calculations are memoized
  • Selective Re-renders: Only necessary components re-render
  • Efficient State Updates: Minimal state updates using shared values

Best Practices

// ✅ Good: Memoize breathing patterns
const breathingPattern = React.useMemo(
() => [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
],
[],
);

// ✅ Good: Memoize callback functions
const handleFinish = React.useCallback(() => {
// Handle completion
}, []);

Testing

Component Testing

import { render, screen } from "@testing-library/react-native";
import { BreathingExercise } from "@breathing-exercise";

describe("BreathingExercise", () => {
const mockProps = {
methods: [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
],
repeat: 1,
onFinish: jest.fn(),
};

it("renders breathing exercise correctly", () => {
render(
<BreathingExercise {...mockProps}>
<BreathingExercise.MethodName />
<BreathingExercise.RemainingTime />
</BreathingExercise>,
);

expect(screen.getByRole("timer")).toBeOnTheScreen();
});

it("displays current method name", () => {
render(
<BreathingExercise {...mockProps}>
<BreathingExercise.MethodName />
</BreathingExercise>,
);

expect(screen.getByText(/inhale/i)).toBeOnTheScreen();
});
});

Animation Testing

import { renderHook } from "@testing-library/react-hooks";
import { useBreathingExercise } from "@breathing-exercise";

describe("useBreathingExercise", () => {
it("provides exercise context", () => {
const wrapper = ({ children }) => (
<BreathingExercise {...mockProps}>{children}</BreathingExercise>
);

const { result } = renderHook(() => useBreathingExercise(), { wrapper });

expect(result.current.progress).toBeDefined();
expect(result.current.methodType).toBe("inhale");
});
});

Common Patterns

Session Management

const useBreathingSession = () => {
const [isActive, setIsActive] = React.useState(false);
const [completedSessions, setCompletedSessions] = React.useState(0);

const startSession = () => setIsActive(true);
const pauseSession = () => setIsActive(false);

const handleFinish = React.useCallback(() => {
setIsActive(false);
setCompletedSessions((prev) => prev + 1);
}, []);

return {
isActive,
completedSessions,
startSession,
pauseSession,
handleFinish,
};
};

Custom Progress Tracking

const BreathingWithAnalytics = () => {
const [analytics, setAnalytics] = React.useState({
startTime: null,
phaseChanges: 0,
});

const handlePhaseChange = React.useCallback((phase) => {
setAnalytics((prev) => ({
...prev,
phaseChanges: prev.phaseChanges + 1,
}));
}, []);

return (
<BreathingExercise
onFinish={() => {
// Send analytics data
console.log("Session analytics:", analytics);
}}
>
{/* Components */}
</BreathingExercise>
);
};

Best Practices

1. Exercise Configuration

// ✅ Good: Define patterns as constants
const BREATHING_PATTERNS = {
BOX: [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 4 },
{ type: "exhale", duration: 4 },
],
RELAXING: [
{ type: "inhale", duration: 4 },
{ type: "hold", duration: 7 },
{ type: "exhale", duration: 8 },
],
} as const;

2. Error Handling

const SafeBreathingExercise = (props) => {
try {
return <BreathingExercise {...props} />;
} catch (error) {
console.error("Breathing exercise error:", error);
return <FallbackComponent />;
}
};

3. Responsive Design

const ResponsiveBreathingExercise = () => {
const { width, height } = useWindowDimensions();
const isLandscape = width > height;

return (
<BreathingExercise style={isLandscape ? styles.landscape : styles.portrait}>
{/* Adaptive layout */}
</BreathingExercise>
);
};

Dependencies

Core Dependencies

  • react: ^18.0.0
  • react-native: ^0.72.0
  • react-native-reanimated: ^3.0.0
  • react-native-paper: ^5.0.0
  • react-i18next: ^13.0.0
  • lottie-react-native: ^6.0.0

Peer Dependencies

  • react-native-svg: ^13.0.0 (for icons)
  • react-native-vector-icons: ^10.0.0 (optional)

TypeScript Support

The module is fully typed with comprehensive TypeScript definitions:

import type {
BreathingExercise,
BreathingExerciseProps,
BreathingExerciseContextType,
BreathingExerciseAnimationProps,
} from "@breathing-exercise";

Migration Guide

From Custom Implementation

If migrating from a custom breathing exercise implementation:

  1. Replace Custom Timer Logic: Use the provided BreathingExerciseProvider
  2. Update Animation Integration: Migrate to the Lottie-based Animation component
  3. Adopt Compound Components: Break down UI into the provided sub-components
  4. Update State Management: Use the useBreathingExercise hook

Breaking Changes

  • v2.0.0: Removed deprecated duration prop in favor of methods array
  • v1.5.0: Changed onComplete to onFinish for consistency

For detailed component-specific documentation, see the individual component pages in the sidebar.