diff --git a/README.md b/README.md
index 04781ba94c..a890970942 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,7 @@
- [**Lifecycles**](./docs/Lifecycles.md)
+ - [`useEvent`](./docs/useEvent.md) — subscribe to events.
- [`useLifecycles`](./docs/useLifecycles.md) — calls `mount` and `unmount` callbacks.
- [`useRefMounted`](./docs/useRefMounted.md) — tracks if component is mounted.
- [`usePromise`](./docs/usePromise.md) — resolves promise only while component is mounted.
diff --git a/docs/useEvent.md b/docs/useEvent.md
new file mode 100644
index 0000000000..71ffc67740
--- /dev/null
+++ b/docs/useEvent.md
@@ -0,0 +1,38 @@
+# `useEvent`
+
+React sensor hook that subscribes a `handler` to events.
+
+
+## Usage
+
+```jsx
+import useEvent from 'react-use/lib/useEvent';
+import useList from 'react-use/lib/useList';
+
+const Demo = () => {
+ const [list, {push}] = useList();
+ const onkeydown = ({key}) => {
+ push(key);
+ };
+ useEvent('keydown', useCallback(onkeydown, []));
+
+ return (
+
+
+ Press some keys on your keyboard.
+
+
+ {JSON.stringify(list, null, 4)}
+
+
+ );
+};
+```
+
+
+## Examples
+
+```js
+useEvent('keydown', handler)
+useEvent('scroll', handler, window, {useCapture: true})
+```
diff --git a/src/__stories__/useEvent.story.tsx b/src/__stories__/useEvent.story.tsx
new file mode 100644
index 0000000000..81f524845b
--- /dev/null
+++ b/src/__stories__/useEvent.story.tsx
@@ -0,0 +1,33 @@
+import {storiesOf} from '@storybook/react';
+import * as React from 'react';
+import {useEvent, useList} from '..';
+import ShowDocs from '../util/ShowDocs';
+import {CenterStory} from './util/CenterStory';
+
+const {useCallback} = React;
+
+const Demo = () => {
+ const [list, {push, clear}] = useList();
+
+ useEvent('keydown', useCallback(({key}) => {
+ if (key === 'r') clear();
+ push(key);
+ }, []));
+
+ return (
+
+
+ Press some keys on your keyboard, r
key resets the list
+
+
+ {JSON.stringify(list, null, 4)}
+
+
+ );
+};
+
+storiesOf('Sensors|useEvent', module)
+ .add('Docs', () => )
+ .add('Demo', () =>
+
+ )
diff --git a/src/__stories__/util/CenterStory.tsx b/src/__stories__/util/CenterStory.tsx
new file mode 100644
index 0000000000..c8ab549448
--- /dev/null
+++ b/src/__stories__/util/CenterStory.tsx
@@ -0,0 +1,15 @@
+import * as React from 'react';
+
+export const CenterStory = ({children}) => (
+
+);
diff --git a/src/index.ts b/src/index.ts
index bef549a943..3517665736 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -9,6 +9,7 @@ import useDropArea from './useDropArea';
import useCounter from './useCounter';
import useCss from './useCss';
import useDebounce from './useDebounce';
+import useEvent from './useEvent';
import useFavicon from './useFavicon';
import useGeolocation from './useGeolocation';
import useGetSet from './useGetSet';
@@ -73,6 +74,7 @@ export {
useCounter,
useCss,
useDebounce,
+ useEvent,
useFavicon,
useGeolocation,
useGetSet,
diff --git a/src/useEvent.ts b/src/useEvent.ts
new file mode 100644
index 0000000000..3b43c24362
--- /dev/null
+++ b/src/useEvent.ts
@@ -0,0 +1,29 @@
+import {useEffect} from 'react';
+
+export interface ListenerType1 {
+ addEventListener (name: string, handler: (event?: any) => void, ...args: any[]);
+ removeEventListener (name: string, handler: (event?: any) => void);
+}
+
+export interface ListenerType2 {
+ on (name: string, handler: (event?: any) => void, ...args: any[]);
+ off (name: string, handler: (event?: any) => void);
+}
+
+export type UseEventTarget = ListenerType1 | ListenerType2;
+
+const defaultTarget = typeof window === 'object' ? window : null;
+
+const useEvent = (name: string, handler?: null | undefined | ((event?: any) => void), target: null | UseEventTarget = defaultTarget, options?: any) => {
+ useEffect(() => {
+ console.log('adding event...');
+ if (!handler) return;
+ if (!target) return;
+ ((target as ListenerType1).addEventListener || (target as ListenerType2).on).call(target, name, handler, options);
+ return () => {
+ ((target as ListenerType1).removeEventListener || (target as ListenerType2).off).call(target, name, handler, options);
+ };
+ }, [name, handler, target, JSON.stringify(options)]);
+};
+
+export default useEvent;