Repository: streamich/react-use
Pull request #292 (2019-05-07 10:34:40)

usePrevious
React state hook that returns the previous state as described in the [React hooks FAQ](https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state).


Patch:
diff --git a/README.md b/README.md
index 3c9da87b..1bf93ae6 100644
--- a/README.md
+++ b/README.md
@@ -115,6 +115,8 @@
   - [`createMemo`](./docs/createMemo.md) &mdash; factory of memoized hooks.
   - [`useGetSet`](./docs/useGetSet.md) &mdash; returns state getter `get()` instead of raw state.
   - [`useGetSetState`](./docs/useGetSetState.md) &mdash; as if [`useGetSet`](./docs/useGetSet.md) and [`useSetState`](./docs/useSetState.md) had a baby.
+  - [`usePrevious`](./docs/usePrevious.md) &mdash; returns the previous state or props.
+
   - [`useObservable`](./docs/useObservable.md) &mdash; tracks latest value of an `Observable`.
   - [`useSetState`](./docs/useSetState.md) &mdash; creates `setState` method which works like `this.setState`. [![][img-demo]](https://codesandbox.io/s/n75zqn1xp0)
   - [`useToggle` and `useBoolean`](./docs/useToggle.md) &mdash; tracks state of a boolean.
diff --git a/docs/usePrevious.md b/docs/usePrevious.md
new file mode 100644
index 00000000..953dee14
--- /dev/null
+++ b/docs/usePrevious.md
@@ -0,0 +1,26 @@
+# `usePrevious`
+
+React state hook that returns the previous state as described in the [React hooks FAQ](https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state).
+
+## Usage
+
+```jsx
+import {usePrevious} from 'react-use';
+
+const Demo = () => {
+  const [count, setCount] = React.useState(0);
+  const prevCount = usePrevious(count);
+
+  return (
+    <p>
+      Now: {count}, before: {prevCount}
+    </p>
+  );
+};
+```
+
+## Reference
+
+```ts
+const prevState = usePrevious = <T>(state: T): T;
+```
diff --git a/src/__stories__/usePrevious.story.tsx b/src/__stories__/usePrevious.story.tsx
new file mode 100644
index 00000000..bcab9ad7
--- /dev/null
+++ b/src/__stories__/usePrevious.story.tsx
@@ -0,0 +1,23 @@
+import { storiesOf } from '@storybook/react';
+import * as React from 'react';
+import { usePrevious } from '..';
+import ShowDocs from './util/ShowDocs';
+
+const Demo = () => {
+  const [count, setCount] = React.useState(0);
+  const prevCount = usePrevious(count);
+
+  return (
+    <div>
+      <p>
+        Now: {count}, before: {String(prevCount)}
+      </p>
+      <button onClick={() => setCount(value => value + 1)}>+</button>
+      <button onClick={() => setCount(value => value - 1)}>-</button>
+    </div>
+  );
+};
+
+storiesOf('State|usePrevious', module)
+  .add('Docs', () => <ShowDocs md={require('../../docs/usePrevious.md')} />)
+  .add('Demo', () => <Demo />);
diff --git a/src/index.ts b/src/index.ts
index 3923f60c..49af3511 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -46,6 +46,7 @@ import useNumber from './useNumber';
 import useObservable from './useObservable';
 import useOrientation from './useOrientation';
 import usePageLeave from './usePageLeave';
+import usePrevious from './usePrevious';
 import usePromise from './usePromise';
 import useRaf from './useRaf';
 import useRefMounted from './useRefMounted';
@@ -119,6 +120,7 @@ export {
   useObservable,
   useOrientation,
   usePageLeave,
+  usePrevious,
   usePromise,
   useRaf,
   useRefMounted,
diff --git a/src/usePrevious.ts b/src/usePrevious.ts
new file mode 100644
index 00000000..658f0f68
--- /dev/null
+++ b/src/usePrevious.ts
@@ -0,0 +1,13 @@
+import { useEffect, useRef } from 'react';
+
+const usePrevious = <T>(state: T): T | undefined => {
+  const ref = useRef<T>();
+
+  useEffect(() => {
+    ref.current = state;
+  });
+
+  return ref.current;
+};
+
+export default usePrevious;


Test patch:
diff --git a/src/__tests__/usePrevious.test.tsx b/src/__tests__/usePrevious.test.tsx
new file mode 100644
index 00000000..70713653
--- /dev/null
+++ b/src/__tests__/usePrevious.test.tsx
@@ -0,0 +1,22 @@
+import { cleanup, renderHook } from 'react-hooks-testing-library';
+import usePrevious from '../usePrevious';
+
+afterEach(cleanup);
+
+describe('usePrevious', () => {
+  it('should be defined', () => {
+    expect(usePrevious).toBeDefined();
+  });
+
+  const hook = renderHook(props => usePrevious(props), { initialProps: 0 });
+
+  it('should return undefined on initial render', () => {
+    expect(hook.result.current).toBe(undefined);
+  });
+
+  it('should return previous state after update', () => {
+    hook.rerender(1);
+    hook.rerender(2);
+    expect(hook.result.current).toBe(1);
+  });
+});