- Published on
- ·11 min read
Resolving Warnings and Errors When Using react-native-tts
I apologize in advance for any awkward expressions in English. 🙏
English is not my native language, and I have relied on ChatGPT's assistance to proceed with the translation.
Introduction
I have decided to add Text-to-Speech (TTS) functionality to my ongoing React Native project, and I've chosen to use the react-native-tts library for this purpose.
For detailed information about the library, you can refer to react-native-tts.
After installing the library and testing it with the code Tts.speak('Hello, world!');
, I confirmed that the speech functionality is working as expected. However, I encountered a warning at the app's startup.
Warning
WARN `new NativeEventEmitter()` was called with a non-null argument without the required `addListener` method.
WARN `new NativeEventEmitter()` was called with a non-null argument without the required `removeListeners` method.
Identifying the Cause
Based on information I found on Stack Overflow, it is likely that the issue is caused by using the latest version of React Native with the react-native-tts library, which lacks the required addListener
and removeListeners
methods for the latest React Native version.
Checking the Library
The version I had installed was 4.1.0, while the latest version of react-native-tts was 4.1.1 as of December 20, 2023.
I attempted to update the library with the command npm i react-native-tts@4.1.1
, but it resulted in an error indicating that it could not be found.
To explore other solutions, I decided to inspect the library's code.
Checking the Library Code
I found that the library's code, specifically in the Git > TextToSpeechModule.java file, includes the missing addListener
and removeListeners
methods that were not present in the module I had installed. Here's how they are implemented:
@ReactMethod
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}
Solution
I added the above code directly to my installed react-native-tts module. I added this code to the end of the node_modules/react-native-tts/android/src/main/java/net/no_mad/tts/TextToSpeechModule.java
file.
⚠ Please note that this code may be removed if the library is updated or if you reinstall the module using npm install
.
After adding the code, I rebuilt the project, and upon checking again, I confirmed that the warning had disappeared.
Tts.removeEventListener Error
After addressing the warning issue, I encountered an error when writing and testing the following code:
useEffect(() => {
const initTts = async () => {
// Omitted
};
const eventStart = (event) => {
console.log('start', event);
}
const eventFinish = (event) => {
console.log('finish', event);
}
const eventCancel = (event) => {
console.log('cancel', event);
}
Tts.addEventListener("tts-start", eventStart);
Tts.addEventListener("tts-finish", eventFinish);
Tts.addEventListener("tts-cancel", eventCancel);
Tts.getInitStatus().then(initTts);
return () => {
Tts.removeEventListener("tts-start", eventStart);
Tts.removeEventListener("tts-finish", eventFinish);
Tts.removeEventListener("tts-cancel", eventCancel);
};
}, []);
Upon leaving the screen and calling Tts.removeEventListener, the following error occurred:
ERROR Warning: Internal React error: Attempted to capture a commit phase error inside a detached tree. This indicates a bug in React. Likely causes include deleting the same fiber more than once, committing an already-finishe
d tree, or an inconsistent return pointer.
Error message:
TypeError: undefined is not a function
in Unknown (created by ...)
Due to an error in Tts.removeEventListener, previous events are not being removed. Consequently, when returning to a screen where Tts is called, events accumulate, resulting in duplicate events triggered by addEventListener.
The following log illustrates this issue when entering the Tts screen for the fourth time and calling Tts.speak only once:
LOG start {"utteranceId": "-42970200"}
LOG start {"utteranceId": "-42970200"}
LOG start {"utteranceId": "-42970200"}
LOG start {"utteranceId": "-42970200"}
LOG finish {"utteranceId": "-42970200"}
LOG finish {"utteranceId": "-42970200"}
LOG finish {"utteranceId": "-42970200"}
LOG finish {"utteranceId": "-42970200"}
Identifying the Cause
I found information stating that in the latest version of React Native, you cannot use removeEventListener
.
removeEventListener
was removed in a recent react-native release. You're supposed to call .remove()
on the subscription returned by addEventListener now.
Solution
Instead of using Tts.removeEventListener
, I modified the code to call .remove()
on the subscriptions returned by Tts.addEventListener.
useEffect(() => {
const initTts = async () => {
// Omitted
};
const eventStart = (event) => {
console.log('start', event);
}
const eventFinish = (event) => {
console.log('finish', event);
}
const eventCancel = (event) => {
console.log('cancel', event);
}
// Receive subscriptions
const subscriptionStart = Tts.addEventListener("tts-start", eventStart);
const subscriptionFinish = Tts.addEventListener("tts-finish", eventFinish);
const subscriptionCancel = Tts.addEventListener("tts-cancel", eventCancel);
Tts.getInitStatus().then(initTts);
return () => {
// Call .remove() here
subscriptionStart.remove();
subscriptionFinish.remove();
subscriptionCancel.remove();
};
}, []);
I can confirm that the error has been resolved, and the events are properly removed when leaving the screen.
TypeScript Issue
After making the change to .remove(), there are no issues with functionality. However, TypeScript shows a red underline and displays the error TS2339: Property 'remove' does not exist on type 'void'
.
I find this error bothersome and would like to resolve it as well.
Identifying the Cause
Upon examining the module, I found that the actual return type of addEventListener
is EmitterSubscription
, but in the index.d.ts
file, it is defined as void
.
// node_modules/react-native-tts/index.js
// Actual implementation
addEventListener(type, handler) {
return this.addListener(type, handler);
}
// node_modules/react-native-tts/index.d.ts
addEventListener: <T extends TtsEvents>(
type: T,
handler: TtsEventHandler<T>
) => void; // <-- This is the problematic part
Solution
To resolve this issue, I opted to override the index.d.ts
file in the current project without directly modifying the module's index.d.ts
file.
I created a react-native-tts
folder at the top level of the project and added an index.d.ts
file with the following content (only changing the return type of addEventListener
to EmitterSubscription
):
// react-native-tts/index.d.ts
declare module "react-native-tts" {
export class ReactNativeTts extends RN.NativeEventEmitter {
getInitStatus: () => Promise<"success">;
requestInstallEngine: () => Promise<"success">;
requestInstallData: () => Promise<"success">;
setDucking: (enabled: boolean) => Promise<"success">;
setDefaultEngine: (engineName: string) => Promise<boolean>;
setDefaultVoice: (voiceId: string) => Promise<"success">;
setDefaultRate: (rate: number, skipTransform?: boolean) => Promise<"success">;
setDefaultPitch: (pitch: number) => Promise<"success">;
setDefaultLanguage: (language: string) => Promise<"success">;
setIgnoreSilentSwitch: (ignoreSilentSwitch: boolean) => Promise<boolean>;
voices: () => Promise<Voice[]>;
engines: () => Promise<Engine[]>;
/** Read the sentence and return an id for the task. */
speak: (utterance: string, options?: Options) => string | number;
stop: (onWordBoundary?: boolean) => Promise<boolean>;
pause: (onWordBoundary?: boolean) => Promise<boolean>;
resume: () => Promise<boolean>;
addEventListener: <T extends TtsEvents>(
type: T,
handler: TtsEventHandler<T>
) => EmitterSubscription; // <-- Only this line changed
removeEventListener: <T extends TtsEvents>(
type: T,
handler: TtsEventHandler<T>
) => void;
}
declare const Tts: ReactNativeTts;
export default Tts;
}
Additionally, for IntelliJ users, if the red underlines persist, you can try clearing the cache by going to File > Invalidate Caches > Invalidate and Restart
with all options checked. This should remove the red underlines.
Conclusion
- Added
addListener
andremoveListener
to the 'node_modules/react-native-tts/android/src/main/java/net/no_mad/tts/TextToSpeechModule.java' file. - Modified to use the
.remove()
method instead ofremoveEventListener
. - Override the '/react-native-tts/index.d.ts' file to return the
EmitterSubscription
type.
Now, all yellow warnings, red errors, and red underlines have been resolved.