
Security News
ECMAScript 2025 Finalized with Iterator Helpers, Set Methods, RegExp.escape, and More
ECMAScript 2025 introduces Iterator Helpers, Set methods, JSON modules, and more in its latest spec update approved by Ecma in June 2025.
react-native-voice-hold
Advanced tools
React Native Voice library with enhanced hold recording functionality and React Native 0.80+ compatibility fixes
Enhanced React Native Voice Recognition Library with Hold Recording Support
A robust React Native voice recognition library with critical bug fixes for React Native 0.76+ and the New Architecture. This package resolves the major event listener issues that break speech-to-text functionality in the latest React Native versions.
DeviceEventEmitter
pattern for reliable event handlingonSpeechResults
callback receives empty array []
despite successful partial resultsstartHoldRecording()
and stopHoldRecording()
methodsnpm install react-native-voice-hold
cd ios && pod install
Add microphone permissions to android/app/src/main/AndroidManifest.xml
:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
import Voice from 'react-native-voice-hold';
import { DeviceEventEmitter } from 'react-native';
const VoiceComponent = () => {
const [results, setResults] = useState<string[]>([]);
const [isListening, setIsListening] = useState(false);
useEffect(() => {
// â
CRITICAL: Use DeviceEventEmitter for RN 0.76+ compatibility
const onSpeechResults = DeviceEventEmitter.addListener(
'onSpeechResults',
(e: any) => {
console.log('đ Speech results:', e.value);
setResults(e.value);
}
);
const onSpeechStart = DeviceEventEmitter.addListener(
'onSpeechStart',
() => {
console.log('đ¤ Speech started');
setIsListening(true);
}
);
const onSpeechEnd = DeviceEventEmitter.addListener(
'onSpeechEnd',
() => {
console.log('đ Speech ended');
setIsListening(false);
}
);
return () => {
onSpeechResults.remove();
onSpeechStart.remove();
onSpeechEnd.remove();
};
}, []);
const startListening = async () => {
try {
await Voice.start('en-US');
} catch (error) {
console.error('â Error starting voice recognition:', error);
}
};
const stopListening = async () => {
try {
await Voice.stop();
} catch (error) {
console.error('â Error stopping voice recognition:', error);
}
};
return (
<View>
<Button
title={isListening ? 'Stop Listening' : 'Start Listening'}
onPress={isListening ? stopListening : startListening}
/>
{results.map((result, index) => (
<Text key={index}>{result}</Text>
))}
</View>
);
};
import Voice from 'react-native-voice-hold';
// Start continuous recording (perfect for voice messages)
const startHoldRecording = async () => {
try {
await Voice.startHoldRecording('en-US', {
// Hold mode disables silence timeouts
continuous: true,
maximumWaitTime: 300000, // 5 minutes max
});
console.log('đī¸ Hold recording started');
} catch (error) {
console.error('â Error starting hold recording:', error);
}
};
// Stop hold recording manually
const stopHoldRecording = async () => {
try {
await Voice.stopHoldRecording();
console.log('âšī¸ Hold recording stopped');
} catch (error) {
console.error('â Error stopping hold recording:', error);
}
};
import { useVoiceRecognition } from 'react-native-voice-hold';
const VoiceAssistant = () => {
const [state, actions] = useVoiceRecognition({
locale: 'en-US',
onStart: () => {
console.log('đ¤ Speech started');
// Clear any existing timeout and reset state
},
onEnd: () => {
console.log('đ Speech ended - processing...');
// đĨ CRITICAL FIX: 1-second timeout ensures transcript processing
// even if final results don't come
},
onResults: results => {
console.log('đ Final results received:', results);
if (results.length > 0) {
// Process final results normally
processTranscript(results[0]);
} else {
// đĨ CRITICAL FIX: Use partial results as fallback
console.log('â ī¸ Final results empty, using partial results fallback');
// Hook automatically handles fallback logic
}
},
onPartialResults: partial => {
console.log('đ Partial results:', partial);
// đĨ CRITICAL FIX: Hook stores partial results for fallback
},
});
// The hook automatically handles all fallback logic!
return (
<View>
<TouchableOpacity
onPressIn={() => actions.startHoldRecording()}
onPressOut={() => actions.stopListening()}
>
<Text>Hold to Speak</Text>
</TouchableOpacity>
</View>
);
};
import React, { useEffect, useState, useRef } from 'react';
import { DeviceEventEmitter, Platform } from 'react-native';
import Voice from 'react-native-voice-hold';
const VoiceComponent = () => {
const [results, setResults] = useState<string[]>([]);
const [isListening, setIsListening] = useState(false);
// đĨ CRITICAL FIX: Add refs for fallback mechanism
const lastPartialResult = useRef<string>('');
const resultsTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const isInterrupted = useRef<boolean>(false);
useEffect(() => {
const onSpeechStart = DeviceEventEmitter.addListener('onSpeechStart', () => {
console.log('đ¤ Speech started');
setIsListening(true);
// Clear any existing timeout
if (resultsTimeoutRef.current) {
clearTimeout(resultsTimeoutRef.current);
resultsTimeoutRef.current = null;
}
lastPartialResult.current = '';
});
const onSpeechEnd = DeviceEventEmitter.addListener('onSpeechEnd', () => {
console.log('đ Speech ended - processing...');
// đĨ CRITICAL FIX: Add timeout to ensure we process transcript
resultsTimeoutRef.current = setTimeout(() => {
console.log('â° Timeout: No final results received, using partial results');
const fallbackTranscript = lastPartialResult.current;
if (fallbackTranscript.trim() && !isInterrupted.current) {
console.log('đ Timeout fallback transcript:', fallbackTranscript);
handleTranscript(fallbackTranscript);
}
}, 1000); // Wait 1 second for final results
});
const onSpeechResults = DeviceEventEmitter.addListener('onSpeechResults', (e) => {
console.log('đ Speech results:', e.value);
// Clear the timeout since we got results
if (resultsTimeoutRef.current) {
clearTimeout(resultsTimeoutRef.current);
resultsTimeoutRef.current = null;
}
if (e.value && e.value.length > 0) {
const finalTranscript = e.value[0];
console.log('⥠Fast transcript:', finalTranscript);
handleTranscript(finalTranscript);
} else {
// đĨ CRITICAL FIX: Use partial results as fallback
console.log('â ī¸ Final results empty, checking partial results...');
const fallbackTranscript = lastPartialResult.current;
if (fallbackTranscript.trim()) {
console.log('đ Using fallback transcript:', fallbackTranscript);
handleTranscript(fallbackTranscript);
}
}
});
const onSpeechPartialResults = DeviceEventEmitter.addListener('onSpeechPartialResults', (e) => {
console.log('đ Partial results:', e.value);
if (e.value && e.value.length > 0) {
const partialTranscript = e.value[0];
// đĨ CRITICAL FIX: Store the last partial result for fallback
if (partialTranscript.trim()) {
lastPartialResult.current = partialTranscript;
}
}
});
return () => {
onSpeechStart.remove();
onSpeechEnd.remove();
onSpeechResults.remove();
onSpeechPartialResults.remove();
// Clear timeout on cleanup
if (resultsTimeoutRef.current) {
clearTimeout(resultsTimeoutRef.current);
resultsTimeoutRef.current = null;
}
Voice.destroy();
};
}, []);
const handleTranscript = (transcript: string) => {
console.log('đ¯ Processing transcript:', transcript);
setResults([transcript]);
};
// ... rest of component
};
Voice.start(locale)
- Start voice recognitionVoice.stop()
- Stop voice recognitionVoice.cancel()
- Cancel voice recognitionVoice.destroy()
- Clean up resourcesVoice.isAvailable()
- Check if voice recognition is availableVoice.isRecognizing()
- Check if currently recognizingVoice.startHoldRecording(locale, options)
- Start continuous recordingVoice.stopHoldRecording()
- Stop hold recording// â
CORRECT: Use DeviceEventEmitter
DeviceEventEmitter.addListener('onSpeechStart', handler);
DeviceEventEmitter.addListener('onSpeechRecognized', handler);
DeviceEventEmitter.addListener('onSpeechEnd', handler);
DeviceEventEmitter.addListener('onSpeechError', handler);
DeviceEventEmitter.addListener('onSpeechResults', handler);
DeviceEventEmitter.addListener('onSpeechPartialResults', handler);
DeviceEventEmitter.addListener('onSpeechVolumeChanged', handler);
// â BROKEN: Direct assignment (doesn't work in RN 0.76+)
Voice.onSpeechResults = handler; // DON'T USE
Install the new package:
npm uninstall @react-native-voice/voice
npm install react-native-voice-hold
Update your imports:
// Old
import Voice from '@react-native-voice/voice';
// New import Voice from 'react-native-voice-hold';
3. **Update event listeners for RN 0.76+ compatibility:**
```typescript
// Old (broken in RN 0.76+)
Voice.onSpeechResults = (e) => console.log(e.value);
// New (works in all RN versions)
DeviceEventEmitter.addListener('onSpeechResults', (e) => console.log(e.value));
// The useVoiceRecognition hook automatically handles this!
// Or implement the fallback pattern shown above for direct usage
Problem: onSpeechResults
receives empty array despite successful partial results.
Solution: â Fixed in this package - The hook and examples above include comprehensive fallback logic.
Problem: Speech events not firing in React Native 0.76+.
Solution: â
Fixed - Use DeviceEventEmitter
pattern instead of direct assignment.
Problem: Hold recording stops unexpectedly.
Solution: â Fixed - Proper hold mode state management prevents premature stopping.
MIT License - see LICENSE file for details.
Contributions are welcome! Please read our contributing guidelines and submit pull requests.
Built with â¤ī¸ for the React Native community
FAQs
React Native Voice library with enhanced hold recording functionality and React Native 0.80+ compatibility fixes
The npm package react-native-voice-hold receives a total of 403 weekly downloads. As such, react-native-voice-hold popularity was classified as not popular.
We found that react-native-voice-hold demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
ECMAScript 2025 introduces Iterator Helpers, Set methods, JSON modules, and more in its latest spec update approved by Ecma in June 2025.
Security News
A new Node.js homepage button linking to paid support for EOL versions has sparked a heated discussion among contributors and the wider community.
Research
North Korean threat actors linked to the Contagious Interview campaign return with 35 new malicious npm packages using a stealthy multi-stage malware loader.