diff --git a/README.md b/README.md
index 3d54b8d1..6bbddad0 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,7 @@
- [__UI__](./docs/UI.md)
- [`useAudio`](./docs/useAudio.md) — plays audio and exposes its controls.
+ - [`useSpeech`](./docs/useSpeech.md) — synthesizes speech from text string.
- [__Animations__](./docs/Animations.md)
diff --git a/docs/useSpeech.md b/docs/useSpeech.md
new file mode 100644
index 00000000..4af79c63
--- /dev/null
+++ b/docs/useSpeech.md
@@ -0,0 +1,18 @@
+# `useSpeech`
+
+React UI hook that synthesizes human voice that speaks a given string.
+
+
+## Usage
+
+```jsx
+import {useSpeech} from 'react-use';
+
+const Demo = () => {
+ const state = useSpeech('Hello world!');
+
+ return (
+
{JSON.stringify(state, null, 2)}
+ );
+};
+```
diff --git a/src/__stories__/useSpeech.story.tsx b/src/__stories__/useSpeech.story.tsx
new file mode 100644
index 00000000..7433cfad
--- /dev/null
+++ b/src/__stories__/useSpeech.story.tsx
@@ -0,0 +1,16 @@
+import {storiesOf} from '@storybook/react';
+import * as React from 'react';
+import {useSpeech} from '..';
+
+const Demo = () => {
+ const state = useSpeech('Hello world!');
+
+ return (
+ {JSON.stringify(state, null, 2)}
+ );
+};
+
+storiesOf('useSpeech', module)
+ .add('Example', () =>
+
+ )
diff --git a/src/index.ts b/src/index.ts
index cd2a00b0..e9cf92a5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -22,6 +22,7 @@ import useOrientation from './useOrientation';
import useRaf from './useRaf';
import useSetState from './useSetState';
import useSize from './useSize';
+import useSpeech from './useSpeech';
import useSpring from './useSpring';
import useTimeout from './useTimeout';
import useTitle from './useTitle';
@@ -55,6 +56,7 @@ export {
useRaf,
useSetState,
useSize,
+ useSpeech,
useSpring,
useTimeout,
useTitle,
diff --git a/src/react.ts b/src/react.ts
index 69defde9..de817490 100644
--- a/src/react.ts
+++ b/src/react.ts
@@ -12,3 +12,6 @@ export const useRef: UseRef = (React as any).useRef;
export type UseCallback = any)>(callback: T, args: any[]) => T;
export const useCallback: UseCallback = (React as any).useCallback;
+
+export type UseMemo = (fn: Function, args: any[]) => T;
+export const useMemo: UseMemo = (React as any).useMemo;
diff --git a/src/useLifecycles.ts b/src/useLifecycles.ts
index b5c7e69b..841ccd47 100644
--- a/src/useLifecycles.ts
+++ b/src/useLifecycles.ts
@@ -1,6 +1,6 @@
import {useEffect} from './react';
-const useLifecycles = (mount, unmount) => {
+const useLifecycles = (mount, unmount?) => {
useEffect(() => {
if (mount) mount();
return () => {
diff --git a/src/useMount.ts b/src/useMount.ts
index 24e72fe5..7ff2edc4 100644
--- a/src/useMount.ts
+++ b/src/useMount.ts
@@ -1,9 +1,5 @@
import {useEffect} from './react';
-const useMount = (mount) => {
- useEffect(() => {
- if (mount) mount();
- }, []);
-};
+const useMount = (mount) => useEffect(mount, []);
export default useMount;
diff --git a/src/useSpeech.ts b/src/useSpeech.ts
new file mode 100644
index 00000000..a7c8a7c3
--- /dev/null
+++ b/src/useSpeech.ts
@@ -0,0 +1,40 @@
+import {useRef} from './react';
+import useSetState from './useSetState';
+import useMount from './useMount';
+
+export interface SpeechState {
+ isPlaying: boolean;
+ volume: number;
+}
+
+export interface SpeechOptions {
+ lang?: any;
+ pitch?: number;
+ rate?: number;
+ voice?: any;
+ volume?: number;
+}
+
+const useSpeech = (text: string, opts: SpeechOptions = {}): SpeechState => {
+ const [state, setState] = useSetState({
+ isPlaying: false,
+ volume: opts.volume || 1,
+ });
+
+ const uterranceRef = useRef(null);
+
+ useMount(() => {
+ const utterance = new SpeechSynthesisUtterance(text);
+ utterance.volume = opts.volume || 1;
+ utterance.onstart = () => setState({isPlaying: true});
+ utterance.onresume = () => setState({isPlaying: true});
+ utterance.onend = () => setState({isPlaying: false});
+ utterance.onpause = () => setState({isPlaying: false});
+ uterranceRef.current = utterance;
+ window.speechSynthesis.speak(uterranceRef.current);
+ });
+
+ return state;
+};
+
+export default useSpeech;