@zeey4d/react-native-sensify
v1.0.0
Published
Unified, declarative React hooks for mobile sensors and gestures in React Native. Supports Expo and bare RN.
Maintainers
Readme
react-native-sensify 🎯
Unified, declarative React hooks for mobile sensors and gestures in React Native.
Simplify sensor data and gesture recognition with clean, TypeScript-first hooks that work across Expo Managed, Expo Bare, and React Native CLI projects.
✨ Features
- 🎯 15 production-ready hooks — sensors, gestures, and smart combinations
- 📱 Cross-platform — iOS + Android
- 🏗️ Works everywhere — Expo Managed, Expo Bare, and bare React Native CLI
- 🔒 TypeScript-first — Full type safety with exported interfaces
- ⚡ Performant — Reanimated worklets run on the UI thread at 60fps
- 🛡️ Error-safe — Graceful degradation when sensors aren't available
- 🧹 Clean — Automatic cleanup on unmount, no memory leaks
📦 Installation
npm install react-native-sensifyPeer Dependencies
You must also install these peer dependencies:
# Expo projects
npx expo install expo-sensors react-native-gesture-handler react-native-reanimated
# Bare React Native CLI projects
npm install expo-sensors react-native-gesture-handler react-native-reanimatedSetup
- react-native-gesture-handler: Wrap your root component with
<GestureHandlerRootView>:
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
{/* Your app */}
</GestureHandlerRootView>
);
}- react-native-reanimated: Add the Babel plugin to your
babel.config.js:
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: ['react-native-reanimated/plugin'],
};🎮 Sensor Hooks
useAccelerometer
Access accelerometer data with built-in shake detection.
import { useAccelerometer } from 'react-native-sensify';
function AccelerometerDemo() {
const { x, y, z, isShaking, shakeMagnitude, available } = useAccelerometer({
interval: 100, // Update every 100ms
shakeThreshold: 1.8, // Adjust shake sensitivity
});
if (!available) return <Text>Accelerometer not available</Text>;
return (
<View>
<Text>X: {x.toFixed(2)}</Text>
<Text>Y: {y.toFixed(2)}</Text>
<Text>Z: {z.toFixed(2)}</Text>
<Text>Magnitude: {shakeMagnitude.toFixed(2)}</Text>
{isShaking && <Text>🔥 SHAKING!</Text>}
</View>
);
}useGyroscope
Access gyroscope rotation rates with automatic tilt direction.
import { useGyroscope } from 'react-native-sensify';
function GyroscopeDemo() {
const { x, y, z, tiltDirection, available } = useGyroscope({
interval: 100,
tiltThreshold: 0.3,
});
return (
<View>
<Text>Rotation: {x.toFixed(2)}, {y.toFixed(2)}, {z.toFixed(2)}</Text>
<Text>Tilt: {tiltDirection}</Text>
{/* tiltDirection: 'left' | 'right' | 'forward' | 'back' | 'neutral' */}
</View>
);
}useMagnetometer
Access magnetometer data with compass heading.
import { useMagnetometer } from 'react-native-sensify';
function CompassDemo() {
const { compassHeading, available } = useMagnetometer({ interval: 200 });
return (
<View>
<Text>Heading: {compassHeading}° from North</Text>
<View style={{ transform: [{ rotate: `${360 - compassHeading}deg` }] }}>
<Text>🧭 N</Text>
</View>
</View>
);
}useShake
Detect shake gestures with a simple callback.
import { useShake } from 'react-native-sensify';
function ShakeDemo() {
useShake(
() => {
Alert.alert('Shake detected! 🎲');
},
{
threshold: 1.5, // G-force threshold
timeout: 1000, // Cooldown between shakes (ms)
}
);
return <Text>Shake your phone!</Text>;
}useTilt
Detect tilt direction with directional callbacks.
import { useTilt } from 'react-native-sensify';
function TiltDemo() {
useTilt({
onLeft: () => console.log('⬅️ Tilted left'),
onRight: () => console.log('➡️ Tilted right'),
onForward: () => console.log('⬆️ Tilted forward'),
onBack: () => console.log('⬇️ Tilted back'),
sensitivity: 0.4, // 0.0 – 1.0
});
return <Text>Tilt your device!</Text>;
}useDeviceOrientation
Detect device orientation with hysteresis.
import { useDeviceOrientation } from 'react-native-sensify';
function OrientationDemo() {
const { orientation, available } = useDeviceOrientation();
// orientation: 'portrait' | 'landscape-left' | 'landscape-right' | 'upside-down'
return (
<View style={{
flexDirection: orientation.startsWith('landscape') ? 'row' : 'column',
}}>
<Text>Orientation: {orientation}</Text>
</View>
);
}👆 Gesture Hooks
All gesture hooks return a gesture object. Wrap your content with <GestureDetector>:
import { GestureDetector } from 'react-native-gesture-handler';
<GestureDetector gesture={gesture}>
<View>{/* your content */}</View>
</GestureDetector>useSwipe
Detect 4-direction swipes with velocity thresholds.
import { useSwipe } from 'react-native-sensify';
import { GestureDetector } from 'react-native-gesture-handler';
function SwipeDemo() {
const { gesture } = useSwipe({
onLeft: () => console.log('← Swiped left'),
onRight: () => console.log('→ Swiped right'),
onUp: () => console.log('↑ Swiped up'),
onDown: () => console.log('↓ Swiped down'),
threshold: 50, // Min distance (px)
velocityThreshold: 500, // Min velocity
});
return (
<GestureDetector gesture={gesture}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Swipe in any direction!</Text>
</View>
</GestureDetector>
);
}usePinch
Detect pinch-in and pinch-out gestures.
import { usePinch } from 'react-native-sensify';
import { GestureDetector } from 'react-native-gesture-handler';
function PinchDemo() {
const { gesture, scale } = usePinch({
onPinchIn: (s) => console.log('Pinching in:', s),
onPinchOut: (s) => console.log('Pinching out:', s),
onPinchEnd: (s) => console.log('Final scale:', s),
});
return (
<GestureDetector gesture={gesture}>
<View style={{ transform: [{ scale }] }}>
<Text>Pinch me! 🤏</Text>
</View>
</GestureDetector>
);
}useLongPress
Detect long press gestures.
import { useLongPress } from 'react-native-sensify';
import { GestureDetector } from 'react-native-gesture-handler';
function LongPressDemo() {
const { gesture } = useLongPress(
() => Alert.alert('Long pressed! 🔴'),
{ duration: 800 } // 800ms hold
);
return (
<GestureDetector gesture={gesture}>
<View style={{ padding: 40, backgroundColor: '#e74c3c', borderRadius: 12 }}>
<Text style={{ color: 'white' }}>Hold me down</Text>
</View>
</GestureDetector>
);
}useDoubleTap
Detect double tap gestures.
import { useDoubleTap } from 'react-native-sensify';
import { GestureDetector } from 'react-native-gesture-handler';
function DoubleTapDemo() {
const { gesture } = useDoubleTap(() => {
console.log('Double tapped! ❤️');
});
return (
<GestureDetector gesture={gesture}>
<View style={{ padding: 40 }}>
<Text>Double tap me!</Text>
</View>
</GestureDetector>
);
}usePan
Track pan gestures with position and velocity.
import { usePan } from 'react-native-sensify';
import { GestureDetector } from 'react-native-gesture-handler';
function PanDemo() {
const { gesture, panData } = usePan({
onStart: (data) => console.log('Started:', data.x, data.y),
onMove: (data) => console.log('Δ:', data.translationX, data.translationY),
onEnd: (data) => console.log('Velocity:', data.velocityX, data.velocityY),
minDistance: 10,
});
return (
<GestureDetector gesture={gesture}>
<View style={{ flex: 1 }}>
<Text>Translation: {panData.translationX.toFixed(0)}, {panData.translationY.toFixed(0)}</Text>
<Text>Velocity: {panData.velocityX.toFixed(0)}, {panData.velocityY.toFixed(0)}</Text>
</View>
</GestureDetector>
);
}useRotate
Detect rotation gestures with angle tracking.
import { useRotate } from 'react-native-sensify';
import { GestureDetector } from 'react-native-gesture-handler';
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
function RotateDemo() {
const { gesture, angle } = useRotate({
onRotate: (a) => console.log('Angle:', a, 'rad'),
});
return (
<GestureDetector gesture={gesture}>
<Animated.View style={{ transform: [{ rotate: `${angle}rad` }] }}>
<Text>🔄 Rotate me!</Text>
</Animated.View>
</GestureDetector>
);
}🧠 Combined / Smart Hooks
useTiltScroll
Tilt the device to scroll content.
import { useTiltScroll } from 'react-native-sensify';
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
function TiltScrollDemo() {
const { scrollY, tiltDirection, available } = useTiltScroll({
sensitivity: 0.4,
maxSpeed: 15,
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateY: -scrollY.value }],
}));
return (
<Animated.View style={animatedStyle}>
<Text>Tilt forward to scroll down, back to scroll up!</Text>
{/* Your scrollable content */}
</Animated.View>
);
}useShakeUndo
Shake to trigger undo with optional confirmation alert.
import { useShakeUndo } from 'react-native-sensify';
function UndoDemo() {
const [actions, setActions] = useState<string[]>([]);
useShakeUndo({
onUndo: () => {
setActions((prev) => prev.slice(0, -1));
},
showAlert: true,
alertTitle: 'Undo Action',
alertMessage: 'Do you want to undo the last action?',
threshold: 1.5,
timeout: 2000,
});
return <Text>Shake to undo! Actions: {actions.length}</Text>;
}useMotionAnimation
Create motion-reactive UI elements driven by device accelerometer.
import { useMotionAnimation } from 'react-native-sensify';
import Animated from 'react-native-reanimated';
function FloatingCardDemo() {
const { animatedStyle, available } = useMotionAnimation({
scaleX: 50, // 50px max X movement
scaleY: 50, // 50px max Y movement
damping: 10, // Spring damping
stiffness: 100, // Spring stiffness
});
return (
<Animated.View
style={[
{
width: 200,
height: 200,
backgroundColor: '#6c5ce7',
borderRadius: 20,
justifyContent: 'center',
alignItems: 'center',
},
animatedStyle,
]}
>
<Text style={{ color: 'white', fontSize: 18 }}>
I move with the device! 🎭
</Text>
</Animated.View>
);
}🔧 Utilities
Permission Handling
import { checkSensorPermission, requestSensorPermission } from 'react-native-sensify';
const hasPermission = await checkSensorPermission();
const granted = await requestSensorPermission();Throttle & Debounce
import { throttle, debounce } from 'react-native-sensify';
const throttled = throttle(() => console.log('throttled'), 200);
const debounced = debounce(() => console.log('debounced'), 300);
// Clean up
throttled.cancel();
debounced.cancel();🧪 Testing Strategy
Unit Tests (Jest)
// Mock expo-sensors for testing
jest.mock('expo-sensors', () => ({
Accelerometer: {
isAvailableAsync: jest.fn().mockResolvedValue(true),
setUpdateInterval: jest.fn(),
addListener: jest.fn().mockReturnValue({ remove: jest.fn() }),
},
// ... same for Gyroscope, Magnetometer
}));
// Mock react-native-gesture-handler
jest.mock('react-native-gesture-handler', () => ({
Gesture: {
Pan: jest.fn().mockReturnValue({
enabled: jest.fn().mockReturnThis(),
onEnd: jest.fn().mockReturnThis(),
onStart: jest.fn().mockReturnThis(),
onUpdate: jest.fn().mockReturnThis(),
minDistance: jest.fn().mockReturnThis(),
}),
// ... same for Pinch, Tap, LongPress, Rotation
},
}));Testing Utilities
npm install --save-dev jest @testing-library/react-native @testing-library/react-hooksE2E Testing
For physical device testing, we recommend:
- Maestro — YAML-based mobile UI testing
- Detox — Gray-box E2E testing for React Native
🚀 Publishing to npm
1. Prepare
# Build the library
npm run build
# Verify the dist/ folder looks correct
ls dist/2. Login to npm
npm login3. Publish
# First publish
npm publish
# Subsequent versions
npm version patch # or minor / major
npm publish4. Versioning Strategy
Follow Semantic Versioning (SemVer):
| Change Type | Version Bump | Example |
|--------------------------|-------------|---------------|
| Bug fixes | patch | 1.0.0 → 1.0.1 |
| New hooks/features | minor | 1.0.0 → 1.1.0 |
| Breaking API changes | major | 1.0.0 → 2.0.0 |
📋 API Reference
Sensor Hooks
| Hook | Returns |
|-------------------------|------------------------------------------------------------|
| useAccelerometer | { x, y, z, isShaking, shakeMagnitude, available } |
| useGyroscope | { x, y, z, tiltDirection, available } |
| useMagnetometer | { x, y, z, compassHeading, available } |
| useShake | void (fires callback) |
| useTilt | void (fires directional callbacks) |
| useDeviceOrientation | { orientation, available } |
Gesture Hooks
| Hook | Returns |
|------------------|------------------------------------------------------------------|
| useSwipe | { gesture } |
| usePinch | { gesture, scale } |
| useLongPress | { gesture } |
| useDoubleTap | { gesture } |
| usePan | { gesture, panData: { x, y, translationX, translationY, ... }} |
| useRotate | { gesture, angle } |
Combined Hooks
| Hook | Returns |
|-----------------------|------------------------------------------------|
| useTiltScroll | { scrollY (SharedValue), tiltDirection, available } |
| useShakeUndo | void (fires onUndo callback) |
| useMotionAnimation | { animX, animY, animZ, animatedStyle, available } |
🤝 Contributing
- Fork the repository
- Create your feature branch:
git checkout -b feature/amazing-hook - Commit your changes:
git commit -m 'Add amazing hook' - Push to the branch:
git push origin feature/amazing-hook - Open a Pull Request
📄 License
MIT © [Your Name]
Made with ❤️ for the React Native community
